前言
适合人群:不用啥特别基础的小白
本文的写作目的在于我发现身边很多人对于缓冲流可能有误区,知道得不够全面,一般会有如下几个观点:
1.缓冲流是用于包装其他流,达到缓冲加速的目的,性能要比普通流要好
2.缓冲流利用了缓冲区,字节一次过读取到缓冲区中,只进行了一次磁盘IO,然后再从缓冲区读取数据,而普通流每读一个字节都需要进行一次磁盘IO,缓冲流大大降低了磁盘IO次数而达到了性能提高的效果。
原理
实际上,以上两个观点都不太对,也不能说全错,大概都对了一半,先回答一下真实情况:
1.缓冲流在大部分情况是要比普通流性能高,这个大部分情况可以粗略认为就是在于读取数据用的数组长度<8196的时候,而在数组长度>8196的情况下很可能性能反而更加低
2.普通流读取数据确实要每个字节都要磁盘IO,但是缓冲流同样也要每个字节磁盘IO先读进缓冲区,然后再从缓冲区里读,并非说用了缓冲流就只需要一次磁盘IO
缓冲流的具体实现其实是这样的:
流程总结如下:
(假设现在有缓冲流bufferedStream,它所包装的普通流为normalStream)
1.缓冲区里初始化一个长度为8196的数组当做缓冲区buf
2.读取数据时先调用normalStream.read(buf),用普通流把数据读进buf,这个步骤说明了缓冲流依然逃不开每读一个字节就需要进行一次IO
3.调用System.arraycopy()方法把数据从缓冲区buf复制到加载数据的数组中
4.另外:如果读取数据的数组长度>8196,那么将会不使用缓冲区,进行直接读取
具体源码就没必要写出来了,在哪里可以看到呢,可以给大家指明一下:
(现在拿BufferedInputStream为例,大家可以自行对比自己去看BufferedOutputStream,我是JDK1.8)
1.流程第一段在183和203行的这一句buf = new byte[size]
2.流程第二段在265和246行的fill()以及里面的getInIfOpen().read(buffer, pos, buffer.length - pos)
3.流程第三段在291行的read1()方法里面的System.arraycopy(getBufIfOpen(), pos, b, off, cnt)
4.流程第四段在282行的read1()方法里面的getInIfOpen().read(b, off, len)
证明
其实上面从理论上已经证明了,现在从实际效果看一看,直接上代码:
public class BufferedStreamTest {
public static void main(String[] args){
File file = new File("C:\\Users\\Administrator\\Desktop\\test.txt");
try {
byte[] buf1 = new byte[128];
byte[] buf2 = new byte[8196];
byte[] buf3 = new byte[8196 * 1024];
readData(file,buf1,false);
readData(file,buf1,true);
readData(file,buf2,false);
readData(file,buf2,true);
readData(file,buf3,false);
readData(file,buf3,true);
}catch (Exception e){
e.printStackTrace();
}
}
public static void readData(File file,byte[] buf,boolean isBuffered)throws Exception{
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bfis = new BufferedInputStream(fis);
InputStream inputStream = isBuffered?bfis:fis;
long startTime = System.currentTimeMillis();
while (inputStream.read(buf)!= -1){
}
System.out.println(inputStream+"--buf_length_"+buf.length+" 耗时为: "+(System.currentTimeMillis() - startTime));
System.out.println("----------------------------------------------------");
}
}
注意点:
1.这里读取的文件test.txt的大小在26MB,由于现在的计算机性能都很好,文件太小效果不明显
2.自己做实验的时候,inputStream不要复用,一定要每一次都要新new的,不要前面已经读取完了,用同一个inputStream相当于没数据可读,耗时会是0
3.为了消除计算机硬件层面的缓存的影响,我都是把普通流写在缓冲流前面,以消除缓冲流读完后因为有了缓存所以普通流才读得快的情况
我们来看看结果:
从结果就能看出来了:
1.当数组长度小于8196,缓冲流明显更快
2.当数组长度等于8196,缓冲流和普通流速度 相当(实际上缓冲流因为附加逻辑判断的原因还会更慢,图上结果缓冲流快2s只是因为计算机底层硬件缓存导致的)
3.当数组长度大于8196,普通流是要快于缓冲流的,因为缓冲流多了许多附加逻辑的原因会有性能损耗,不过倒是很轻微的损耗
结论
1.缓冲流不一定比普通流快,但是慢的时候也慢不了多少
2.缓冲流也要进行多次磁盘IO,这取决于缓冲流所包装的普通流的read()方法的实现了,因为本质就是调用了所包装的普通流的read(buf)
在实际开发建议多使用缓冲流没什么问题,它为程序员屏蔽了很多细节,让大家不用考虑太多就有一种“缓冲流可以加速”的体验感;当然了,如果经验比较丰富的话,而且对内存空间以及极限速度有更大追求的话,缓冲流就不一定是个好选择,可以根据需要去实现自己IO模型。