文件io的坑

API的误解

最近被同事问到一个问题,就是他做的文件下载功能下载下来的结果不对,在review代码的时候,发现了如下的写法。

    byte[] bytes = new byte[1024];
    int len = 0;
    while((len = in.read(bytes))>0){
           os.write(bytes);
    }

然后就把bytes写入文件了。然后最后数据多了,这个问题,主要是没有仔细看java的api。

public int read(byte[] b) throws IOException

read方法接受一个数组,并且返回一个int类型的长度,表示这次读取了多少数据。多出数据的原因就是没有获取到返回的长度进行合理的获取。
下面简单说一下不检验长度的问题。
byte[] b=new byte[5]第一次从读取文件返回返回以后被填满。
{1,2,3,4,5}
下次没有5个数据只有三个了
{6,7,8,4,5}
一直使用这个byte会发现,下次数据如果写入byte的全部的长度的话,就会有脏数据,{4,5}。这就是问题的原因,没有注意方法的返回值。
如果是一个正常的java文件拷贝,例如下面的代码

 byte[] bytes = new byte[1024];
 while ((i = bis.read(bytes)) >0 ) {
        os.write(bytes, 0, i);
 }

每次需要读取到返回值,然后写入的时候,然后把数组的值,按照长度截取传入。

最佳实践

java7的特性nio2–Files

static long	copy(InputStream in, Path target, CopyOption... options)
static long	copy(Path source, OutputStream out)
static Path	copy(Path source, Path target, CopyOption... options)

在java7 nio的特性里,提供了丰富的拷贝的方式。底层都是通过jni实现的拷贝,要比java中写byte数组拷贝要更高效,减少了对象从堆内存到系统内存的折腾。
顺带说一下,nio2其他的比较好用的拷贝文件可能用到的方法。

static InputStream	newInputStream(Path path, OpenOption... options)
static OutputStream	newOutputStream(Path path, OpenOption... options)

用静态方法获取输入输出流。通过这个方法只是让代码更短,看着舒服。这个相当于静态工厂了。

static byte[]	readAllBytes(Path path)
static List<String>	readAllLines(Path path, Charset cs)

直接获取到文件所有数组以及所有行。这里不用再去考虑什么文件关闭的问题。比try-with-resource还简单。

static Path	createFile(Path path, FileAttribute<?>... attrs)
static void	delete(Path path)
static Path	move(Path source, Path target, CopyOption... options)

普通的文件操作。
nio2带来了优势主要是在一些特殊功能上,一方面是有更高效的jni实现,一方面是静态方法使得调用的代码更整洁。
如果jdk在7以下。就需要考虑common io的封装的操作。性能上可能没有提升,但是也能使得调用代
common io
CopyUtils

       Method      Input               Output          Dependency
       ------      -----               ------          -------
 1     copy        InputStream         OutputStream    (primitive)
 2     copy        Reader              Writer          (primitive)
 3     copy        InputStream         Writer          2
 4     copy        Reader              OutputStream    2
 5     copy        String              OutputStream    2
 6     copy        String              Writer          (trivial)
 7     copy        byte[]              Writer          3
 8     copy        byte[]              OutputStream    (trivial)

common io有更多的封装,有InputStream到OutputStream的拷贝,这个可以直接应用到网络io。

common io关闭的方式需要注意

 try {
     return IOUtils.copy(inputStream, outputStream);
 } finally {
     IOUtils.closeQuietly(inputStream, outputStream);
 }

总结

java7已经是提供了很强大的功能了,在不依赖其他库的时候是不错的选择。如果可以引用common io。建议可以考虑common io。因为他封装的更多,应对复杂的场景,可以写较少的代码。

点赞