流和缓冲区
Buffer即缓冲区,是包含有一定量数据的容器。有些人认为流就是缓冲区,其实非也。流是随着时间产生的数据序列,而缓冲区顾名思义就是起缓冲作用的,缓冲的本质是排队,流的本质是数据。缓冲区我们可以理解为一个水管,满足FIFO,如果我们不设置缓冲区,那么就会增加系统的不稳定性和安全性。比如系统对接过程中,不设置缓冲区,对于其它系统作出的请求都得立即响应,疲于奔命直至系统崩溃。而增加缓冲区成本低,可以批量处理,效果杠杠的。
在Java我们比较熟悉的数据流主要包含四种基本的类,InputStream、OutputStream、Reader及Writer类,它们分别处理字节流和字符流,接下来我们将通过文件写入和读取的例子来熟悉流和缓冲区的差别及使用。
文件读取和写入
我们在进行文件读取的时候,可以通过FileInputStream(继承InputStream抽象类)通过字节的方式读取文件,适合读取所有类型的文件(图像、视频、文本文件等)。Java也提供了FileReader专门读取文本文件。
FileOutputStream(继承OutputStream抽象类) 通过字节的方式写数据到文件中,适合所有类型的文件。Java也提供了FileWriter专门写入文本文件。
Random random=new Random();
String fileName="word";
FileOutputStream out=new FileOutputStream(fileName);
long start=System.currentTimeMillis();
for(int i=0;i<1000000;i++){
for(int j=0;j<5;j++){
out.write(97+random.nextInt(5));
}
}
out.close();
// 此种写入方式写入输出时间:13765 ms
System.out.println(System.currentTimeMillis()-start);
String fileName="word";
FileInputStream in=new FileInputStream(fileName);
long start=System.currentTimeMillis();
while(in.read()!=-1){
}
in.close();
// 此种方式读取输出时间:9705ms
System.out.println(System.currentTimeMillis()-start);
缓冲字节流
当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。BufferedInputStream和BufferedOutputStream这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率。
接下来,我们将上面读取和写入的代码通过缓冲字节流来优化下,看看能提升多快:
Random random=new Random();
String fileName="word";
// 缓冲空间大小可设置,不设置默认值8192
int bufferSize=8*1024;
BufferedOutputStream out=new BufferedOutputStream(new FileOutputStream(fileName),bufferSize);
//FileOutputStream out=new FileOutputStream(fileName);
long start=System.currentTimeMillis();
for(int i=0;i<1000000;i++){
for(int j=0;j<5;j++){
out.write(97+random.nextInt(5));
}
}
out.close();
// 此种写入方式写入输出时间:246 ms
System.out.println(System.currentTimeMillis()-start);
Random random=new Random();
String fileName="word";
BufferedInputStream in=new BufferedInputStream(new FileInputStream(fileName));
//FileInputStream in=new FileInputStream(fileName);
long start=System.currentTimeMillis();
while(in.read()!=-1){
}
in.close();
// 此种方式读取输出时间:206ms
System.out.println(System.currentTimeMillis()-start);
通过比较两者执行的速度,发现速度提升明显。而在读取的时候我们也可以不用Buffer这种方式,通过主动的设置缓冲区来实现,如下:
FileInputStream in=new FileInputStream(fileName);
byte[] bytes=new byte[1024*4];
while(in.read(bytes)!=-1){
}
NIO实现缓冲
当然,我们也可通过NIO实现,NIO如字面上的意思就是NEW IO。一种新的IO读取和写入的标准,你说速度和上面的Buffer有无区别,我只能说并无区别,只不过下面提供了一套标准,我们要按照它的标准去设置Buffer。在上面读取代码基础上实现如下:
String fileName="word";
// 获取channel
FileChannel channel=new FileInputStream(fileName).getChannel();
// 设置缓冲
ByteBuffer buff=ByteBuffer.allocate(4*1024);
long start=System.currentTimeMillis();
while(channel.read(buff)!=-1){
// 翻转 提供读取
buff.flip();
//System.out.println(new String(buff.array()));
// 清空
buff.clear();
}
// 此种方式读取返回时间26ms
System.out.println(System.currentTimeMillis()-start);
上面的方法和清空可能理解起来比较费劲,我们可以看看buffer具体实现原理来看看。
一个单向缓冲区实现结构如下:
P、L、C分别代表是指针指向当前位置、实际限制位置和物理限制位置。
在翻转操作时我们可以发现P重新指向了起始位置,L指向了最后一个字母后面,表示可以读取,而clear操作了P指向了起始位置,使L指向C位置,可以替换。
所以通过上面几种方法来操作指针来达到修改缓冲区的目的。