IO第八回:处理流之二:缓冲流
标签: IO流
什么是处理流
处理流能够增强功能,提供性能,作用在节点流之上。
节点流与处理流的关系如下:
节点流(字节流、字符流)处于IO操作的第一线,所有操作必须通过它们进行;
处理流可以对其他流进行处理(提高效率或操作灵活性)。
处理流包括:缓冲流、转换流、对象流、打印流等。
缓冲流
java缓冲流本身不具IO功能,缓冲流要“套接”在相应的节点流之上,像是为别的流装上一种包装,对读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法。
当对文件或其他目标频繁读写或操作效率低,效能差。这时使用缓冲流能够更高效的读写信息。因为缓冲流先将数据缓存起来,然后一起写入或读取出来。所以说,缓冲流很重要的,在IO操作时加上缓冲流提升性能,对于输出的缓冲流,写出的数据会先在内存中缓存,使用flush()将会使内存中的数据立刻写出。
根据数据操作单位可以把缓冲流分为字节缓冲流和字符缓冲流:
- 字节缓冲流为:
BufferedInputStream—字节输入缓冲流
BufferedOutputStream—字节输出缓冲流 - 字符缓冲流为:
BufferedReader—字符输入缓冲流
BufferedWriter—字符输出缓冲流
下面主要介绍这四种缓冲流的使用。
字节缓冲流
BufferedInputStream—字节输入缓冲流
public class BufferedInputStream extends FilterInputStream
BufferedInputStream为另一个输入流添加了功能,即缓冲输入和支持mark和reset方法的功能。 当创建BufferedInputStream时,将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节。 mark操作会记住输入流中的一点,并且reset操作会导致从最近的mark操作之后读取的所有字节在从包含的输入流中取出新的字节之前重新读取。
BufferedInputStream 本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。
成员变量
protected byte[] buf
存储数据的内部缓冲区数组。protected int count
指当前缓冲区的有效字节数,而不是输入流中的有效字节数。protected int marklimit
标记的最大值。protected int markpos
当前缓冲区的标记位置。protected int pos
当前缓冲区的位置索引,而不是输入流中的位置索引。
方法摘要
int available()
返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。void close()
关闭此输入流并释放与流相关联的任何系统资源。void mark(int readlimit)
见的总承包 mark的方法 InputStream 。boolean markSupported()
测试这个输入流是否支持 mark和 reset方法。int read()
见 read法 InputStream的一般合同。int read(byte[] b, int off, int len)
从给定的偏移开始,将字节输入流中的字节读入指定的字节数组。void reset()
见 reset法 InputStream的一般合同。long skip(long n)
见 skip法 InputStream的一般合同。
源码思想(重要)
要想读懂BufferedInputStream的源码,就要先理解它的思想。BufferedInputStream的作用是为其它输入流提供缓冲功能。创建BufferedInputStream时,我们会通过它的构造函数指定某个输入流为参数。BufferedInputStream会将该输入流数据分批读取,每次读取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从输入流中读取下一部分的数据。
为什么需要缓冲呢?原因很简单,效率问题!缓冲中的数据实际上是保存在内存中,而原始数据可能是保存在硬盘或NandFlash等存储介质中;而我们知道,从内存中读取数据的速度比从硬盘读取数据的速度至少快10倍以上。
那干嘛不干脆一次性将全部数据都读取到缓冲中呢?第一,读取全部的数据所需要的时间可能会很长。第二,内存价格很贵,容量不像硬盘那么大。
源码分析(重要)
package java.io;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class BufferedInputStream extends FilterInputStream {
// 默认的缓冲大小是8192字节
// BufferedInputStream 会根据“缓冲区大小”来逐次的填充缓冲区;
// 即,BufferedInputStream填充缓冲区,用户读取缓冲区,读完之后,BufferedInputStream会再次填充缓冲区。如此循环,直到读完数据...
private static int defaultBufferSize = 8192;
// 缓冲数组
protected volatile byte buf[];
// 缓存数组的原子更新器。
// 该成员变量与buf数组的volatile关键字共同组成了buf数组的原子更新功能实现,
// 即,在多线程中操作BufferedInputStream对象时,buf和bufUpdater都具有原子性(不同的线程访问到的数据都是相同的)
private static final
AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf");
// 当前缓冲区的有效字节数。
// 注意,这里是指缓冲区的有效字节数,而不是输入流中的有效字节数。
protected int count;
// 当前缓冲区的位置索引
// 注意,这里是指缓冲区的位置索引,而不是输入流中的位置索引。
protected int pos;
// 当前缓冲区的标记位置
// markpos和reset()配合使用才有意义。操作步骤:
// (01) 通过mark() 函数,保存pos的值到markpos中。
// (02) 通过reset() 函数,会将pos的值重置为markpos。接着通过read()读取数据时,就会从mark()保存的位置开始读取。
protected int markpos = -1;
// marklimit是标记的最大值。
// 关于marklimit的原理,我们在后面的fill()函数分析中会详细说明。这对理解BufferedInputStream相当重要。
protected int marklimit;
// 获取输入流
private InputStream getInIfOpen() throws IOException {
InputStream input = in;
if (input == null)
throw new IOException("Stream closed");
return input;
}
// 获取缓冲
private byte[] getBufIfOpen() throws IOException {
byte[] buffer = buf;
if (buffer == null)
throw new IOException("Stream closed");
return buffer;
}
// 构造函数:新建一个缓冲区大小为8192的BufferedInputStream
public BufferedInputStream(InputStream in) {
this(in, defaultBufferSize);
}
// 构造函数:新建指定缓冲区大小的BufferedInputStream
public BufferedInputStream(InputStream in, int size) {