Java IO精髓:高效块读写入技术深入解析
文件的复制
首先我们先用昨天学习的知识来完成文件的复制,使用read和write方法来完成。
public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("./image.png");FileOutputStream fos = new FileOutputStream("./copy_image.png");int d;int num = 0;long startTime = System.currentTimeMillis();
// d = fis.read();
// fos.write(d+1);哪怕改一个值图片都无法正常显示
//如果直接修改数据流中的任意字节(如你尝试的fos.write(d+1);),这很可能导致生成的图片文件损坏,
// 因为图片的格式(如PNG, JPEG等)有严格的字节排列和数据结构要求。
// 一个字节的改动可能会破坏图片的头部信息、颜色表、像素数据或其他关键部分。while ((d = fis.read()) != -1) {fos.write(d);num++;}long endTime=System.currentTimeMillis();System.out.println("复制完毕");System.out.println(num);System.out.println("复制时间为:"+(endTime-startTime)+"毫秒");fis.close();fos.close();}
在昨天的学习中,我们学习了如何向文件中写入或者从文件中读取数据,但是在面临需要读入或者写出大量数据时,例如上面我们要复制一张图片,或者是一首歌,又或者是一个视频时,我们需要不断的用循环来调用read和write方法?很明显这样做很浪费性能,并且上述案例在复制文件时的读写效率是很低的。因为硬盘的特性,决定着硬盘的读写效率差,而单字节读 写就是在频繁调用硬盘的读写,从而产生了"木桶效应"。 为了解决这个问题,我们可以采取使用块读写的方式来复制文件,减少硬盘的实际读写的次数,从而提 高效率。
块读入
InputStream定义了块读方法
int read(byte[ ] data)
一次性读取给定字节数组data总长度的字节量并存入到该数组中。 返回值表达实际读取到了多少字节。如果返回值为-1表达流读取到末尾了
下面我将用拆分来详细解释块读入是如何运行的
原文件内容:11001100 01010101 10101010 00111100 11001111 00110011 11101100--------------------------------------------------------------byte[] data = new byte[3];//长度为3的字节数组data:{00000000,00000000,00000000}int len=0;第一次调用:len = fis.read(data);//因为data长度为3,因此一次性读取3字节并存入该数组原文件内容:11001100 01010101 10101010 00111100 11001111 00110011 11101100^^^^^^^^ ^^^^^^^^ ^^^^^^^^连续读取3个字节data:{11001100,01010101,10101010}读取后数组内容就是读取到的内容len:3 len接收的返回值为整数,表示本次实际读取到了3个字节-----------------------------------------------------------------第二次调用:len = fis.read(data);//因为data长度为3,因此一次性读取3字节并存入该数组原文件内容:11001100 01010101 10101010 00111100 11001111 00110011 11101100^^^^^^^^ ^^^^^^^^ ^^^^^^^^连续读取3个字节data:{00111100,11001111,00110011}读取后数组内容就是读取到的内容len:3 len接收的返回值为整数,表示本次实际读取到了3个字节----------------------------------------------------------------第三次调用:len = fis.read(data);//因为data长度为3,因此一次性读取3字节并存入该数组原文件内容:11001100 01010101 10101010 00111100 11001111 00110011 11101100^^^^^^^^ ^^^^^^^^ ^^^^^^^^应当读取3个,实际读取到1个连续读取3个字节data:{11101100,11001111,00110011}本次读取 |--上次的旧数据---|len:1 len接收的返回值为整数,表示本次实际读取到了1个字节-----------------------------------------------------------------第四次调用:len = fis.read(data);//因为data长度为3,因此一次性读取3字节并存入该数组原文件内容:11001100 01010101 10101010 00111100 11001111 00110011 11101100^^^^^^^^ ^^^^^^^^ ^^^^^^^^本次没有数据可读(EOF)data:{11101100,11001111,00110011}|------上次的旧数据--------|len:-1 已经是文件末尾了
块写出
OutputStream中定义了块写操作
void write(byte[ ] data)
一次性将给定字节数组data中的所有字节写出
小问题
在学习完上面的两个方法后相信你已经学会如何把这张图片去复制了,但是如果你去实践后,就会发现速度问题解决了,但是复制后的文件比原文件大一些。这是文件不一定是10240的倍数,这会导致最后 一次读取时是读不够10240的字节数的,那么data数组中就不是所有数据都是新数据了。此时如果在写 出时将data数组所有内容写出就会出现文件最后多出很多旧的数据。
我们用一张图片来解释
我们每次都是读入四个字节的数据,但是在复制到第四次也就是最后一次时只剩下紫色部分的数据,而显然这不能填满data数组的长度,因此再最后写入复制文件时后面的数据还是蓝色的也就是上次复制时剩下的旧数据。
解决
使用OutputStream的另一个块写操作
void write(byte[] data,int offset,int len)
将给定数组data从offset处开始的连续len个字节一次性写
我们来看看最后的解决
FileInputStream fis = new FileInputStream("./image.png");FileOutputStream fos = new FileOutputStream("./copy_image02.png");byte[] data = new byte[1024 * 100];int len = 0;long start=System.currentTimeMillis();while ((len = fis.read(data)) != -1) {fos.write(data,0,len);}long end=System.currentTimeMillis();System.out.println("耗时:"+(end-start)+"毫秒");fos.close();fis.close();