BufferedInputStream是缓冲输入流,作用是为另一个输入流添加一些功能,比如缓冲输入功能以及支持mark和reset方法的能力。在创建BufferedInputStream时,会在内存中创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流一次性填充多个字节到该内部缓冲区。当程序需要读取字节时,直接从内部缓冲区中读取。当内部缓冲区中数据被读完后,会再次从包含的输入流一次性填充多个字节到该内部缓冲区。mark操作会记录输入流中的某个点,reset操作使得在从输入流中获取新字节之前,再次读取自最后一次mark操作后读取的所有字节。
BufferedOutputStream是缓冲输出流,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
BufferedInputStream
public class BufferedInputStream extends FilterInputStream {
//缓冲区默认的默认大小
private static int DEFAULT_BUFFER_SIZE = 8192;
/**
* 分派给arrays的最大容量
* 为什么要减去8呢?
* 因为某些VM会在数组中保留一些头字,尝试分配这个最大存储容量,
* 可能会导致array容量大于VM的limit,最终导致OutOfMemoryError。
*/
private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
/**
* 存放数据的内部缓冲数组。
* 当有必要时,可能会被另一个不同容量的数组替代。
*/
protected volatile byte buf[];
/**
* 为缓冲区提供compareAndSet的原子更新器。
* 这是很有必要的,因为关闭操作可以使异步的。我们使用非空的缓冲区数组作为流被关闭的指示器。
* 该成员变量与buf数组的volatile关键字共同作用,实现了当在多线程环境中操作BufferedInputStream对象时,buf和bufUpdater都具有原子性。
*/
private static final
AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf");
/**
* 缓冲区中的字节数。
*/
protected int count;
/**
* 缓冲区当前位置的索引
*/
protected int pos;
/**
* 最后一次调用mark方法时pos字段的值。
*/
protected int markpos = -1;
/**
* 调用mark方法后,在后续调用reset方法失败之前所允许的最大提前读取量。
* markpos的最大值
*/
protected int marklimit;
/**
* 获取输入流。
* 判断输入流是否为null,如果为null,抛出异常,否则返回输入流。
*/
private InputStream getInIfOpen() throws IOException {
InputStream input = in;
if (input == null)
throw new IOException("Stream closed");
return input;
}
/**
* 获取缓冲区数组。
* 检查缓冲区数组是否为null,如果为null,抛出异常,否则返回缓冲区数组。
*/
private byte[] getBufIfOpen() throws IOException {
byte[] buffer = buf;
if (buffer == null)
throw new IOException("Stream closed");
return buffer;
}
/**
* 构造方法之一
* 创建一个缓冲区大小为DEFAULT_BUFFER_SIZE的BufferedInputStream。
*/
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
/**
* 构造方法之一。
* 创建一个缓冲区大小为size的BufferedInputStream。 *
*/
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
/**
* 填充缓冲区。
*/
private void fill() throws IOException {
//获取缓冲区数组
byte[] buffer = getBufIfOpen();
if (markpos < 0)// case1:缓冲区没有被标记。如果缓冲区被标记,那么markpos肯定大于等于0
pos = 0;:
else if (pos >= buffer.length) //缓冲区被标记,且缓冲器已满。pos >= buffer.length说明缓冲区已满。
if (markpos > 0) { //case2:缓冲区被标记且标记位置大于0,且缓冲器已满。/* can throw away early part of the buffer */
//获取被标记位置和缓冲区末尾之间的长度
int sz = pos - markpos;
//将buffer中从markpos开始的数据拷贝到buffer中(从位置0开始填充,填充长度是sz)
System.arraycopy(buffer, markpos, buffer, 0, sz);
//将缓存区当前位置定位为sz
pos = sz;
//将标记位置定位为0
markpos = 0;
} else if (buffer.length >= marklimit) {//case3:缓冲区被标记且标记位置等于0,且缓冲器已满,且缓冲区太大导致标记无效
//将标记位置定位为-1
markpos = -1;
//将当前位置定位为0
pos = 0;
} else if (buffer.length >= MAX_BUFFER_SIZE) {//case4:缓冲区被标记且标记位置等于0,且缓冲器已满,且缓冲区超出允许范围
//抛出异常
throw new OutOfMemoryError("Required array size too large");
} else {//case5:不确定这是什么情况,但从下面的代码中可以知道这种情况下需要对缓冲区进行扩容/* grow buffer */
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?//
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
//从输入流中读取数据,将缓冲区填满
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
/**
* 从缓冲区中读取下个字节
*
* 如果已到输入流末尾,返回-1。
*/
public synchronized int read() throws IOException {
//pos >= count,说明已经读到了缓冲区末尾,这时应该从输入轮流中读取一批数据来填充缓冲区
if (pos >= count) {
fill();
//已经执行了填充数据的方法,缓冲区中还是没有数据可读,说明根本就没有数据被填充到缓冲区,这种情况一般是输入流已经没有数据可读,返回-1
if (pos >= count)
return -1;
}
//获取缓冲区,从缓冲区中读取下个字节
return getBufIfOpen()[pos++] & 0xff;
}
/**
* 从此字节输入流中给定偏移量off处开始将len个字节读取到指定的byte数组中。
*
* @param b 目标字节数组
* @param off 起始偏移量
* @param len 读取的最大字节数。
* @return 读入b的总字节数,如果由于已到达流末尾而不再有数据,则返回-1。
*/
private int read1(byte[] b, int off, int len) throws IOException {
//计算缓冲区中剩余可读的字节数
int avail = count - pos;
//如果缓冲区中没有可读的字节
if (avail <= 0) {
/* If the requested length is at least as large as the buffer, and
if there is no mark/reset activity, do not bother to copy the
bytes into the local buffer. In this way buffered streams will
cascade harmlessly. */
//如果读取的长度大于缓冲区长度且没有做标记
if (len >= getBufIfOpen().length && markpos < 0) {
//直接从输入流中读数据到b中
return getInIfOpen().read(b, off, len);
}
//填充缓冲区
fill();
//再次计算缓冲区中可读字节数
avail = count - pos;
//如果缓冲区中依然没有可读字节数,说明已经到达流末尾,返回-1
if (avail <= 0) return -1;
}
//计算实际要读取的字节数
int cnt = (avail < len) ? avail : len;
//从缓冲区的pos位置将cnt个字节读取到b中
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
//缓冲区当前位置+cnt
pos += cnt;
//返回实际读取的字节数
return cnt;
}
/**
* 从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。
*
* @param b 目标字节数组
* @param off 起始偏移量
* @param len 读取的最大字节数。
* @return 读入b的总字节数,如果由于已到达流末尾而不再有数据,则返回-1。
*/
public synchronized int read(byte b[], int off, int len)
throws IOException
{
//检查输入流是否关闭
getBufIfOpen();
//检查参数是否合法
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
//读取到指定长度的数据才返回
int n = 0;
for (;;) {
int nread = read1(b, off + n, len - n);
if (nread <= 0)
return (n == 0) ? nread : n;
n += nread;
if (n >= len)
return n;
// if not closed but no bytes available, return
InputStream input = in;
if (input != null && input.available() <= 0)
return n;
}
}
/**
* 跳过和丢弃此输入流或缓冲区中的n个字节。
* 返回实际跳过的字节数
*/
public synchronized long skip(long n) throws IOException {
//检查输入流是否关闭
getBufIfOpen();
//如果参数不合法,返回0
if (n <= 0) {
return 0;
}
//缓冲区中可读的字节数
long avail = count - pos;
//如果avail<=0,即已经到达缓冲区末尾
if (avail <= 0) {
// 如果没有调用过mark方法,直接在输入流中跳过n个字节
if (markpos <0)
return getInIfOpen().skip(n);
// 填充缓冲区
fill();
//重新计算可以读取的字节数
avail = count - pos;
//如果依然小于0,说明已经到了输入流末尾,没数据可读了,返回0
if (avail <= 0)
return 0;
}
//计算实际跳过的字节数
long skipped = (avail < n) ? avail : n;
//在缓冲区中跳过skipped个字节
pos += skipped;
//返回实际跳过的字节
return skipped;
}
/**
* 返回可以从此输入流读取(或跳过)、且不受此输入流接下来的方法调用阻塞的估计字节数。
*/
public synchronized int available() throws IOException {
//计算缓冲区可读的字节数
int n = count - pos;
//获取输入流中可读的字节数
int avail = getInIfOpen().available();
//?
return n > (Integer.MAX_VALUE - avail)
? Integer.MAX_VALUE
: n + avail;
}
/**
* 在此缓冲区中标记当前的位置。对reset方法的后续调用会在最后标记的位置重新定位此缓冲区,以便后续读取重新读取相同的字节。
*
* readlimit告知此缓冲区在标记位置失效之前允许读取的字节数。
*
* mark的一般约定是:如果方法markSupported返回true,那么缓冲区总是在调用mark之后记录所有读取的字节,并时刻准备在调用方法reset时(无论何时),再次提供这些相同的字节。但是,如果在调用reset之前可以从流中读取多于readlimit的字节,则不需要该流记录任何数据。
*
* @param readlimit 在标记位置失效前可以读取字节的最大限制。
* @see java.io.BufferedInputStream#reset()
*/
public synchronized void mark(int readlimit) {
marklimit = readlimit;
markpos = pos;
}
/**
* 将此缓冲区重新定位到最后一次对此输入流调用mark方法时的位置。
*
* 如果创建流以后未调用方法mark,或最后调用mark以后从该流读取的
* 字节数大于最后调用mark时的参数,则可能抛出IOException。
*
* 如果未抛出这样的IOException,将此缓冲区重新定位到最后一次对
* 此缓冲区调用mark方法时的位置。
*
* @exception IOException 如果未标记或该标记失效。
* @see java.io.BufferedInputStream#mark(int)
* @see java.io.IOException
*/
public synchronized void reset() throws IOException {
getBufIfOpen(); // Cause exception if closed
if (markpos < 0)
throw new IOException("Resetting to invalid mark");
pos = markpos;
}
/**
* 测试此输入流是否支持mark和reset方法。
* 是否支持mark和reset是特定输入流实例的不变属性。
*/
public boolean markSupported() {
return true;
}
/**
* 关闭此输入流并释放与该流关联的所有系统资源。
* 关闭了该流之后,后续的read()、available()、reset()或 skip()调用都将抛出IOException。
* 关闭之前已关闭的流不会产生任何效果。
*/
public void close() throws IOException {
byte[] buffer;
while ( (buffer = buf) != null) {
if (bufUpdater.compareAndSet(this, buffer, null)) {
InputStream input = in;
in = null;
if (input != null)
input.close();
return;
}
// Else retry in case a new buf was CASed in fill()
}
}
}
BufferedOutputStream
public
class BufferedOutputStream extends FilterOutputStream {
//用于存储数据的内部缓冲区
protected byte buf[];
/**
* 缓冲区中的有效字节数。
*/
protected int count;
/**
* 构造函数之一。
* 创建缓冲区大小为8192的新的缓冲输出流
*/
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
/**
* 构造函数之一。
* 创建缓冲区大小为size的新的缓冲输出流。
*
* @param out 指定输出流
* @param size 缓冲区大小
* @exception IllegalArgumentException 如果size<=0
*/
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
/**
* 刷新缓冲区。
* 将缓冲区中所有字节写入此缓冲输出流,并清空缓冲区
*/
private void flushBuffer() throws IOException {
if (count > 0) {
//将缓冲区中所有字节写入此输出流。
out.write(buf, 0, count);
//清空缓冲区
count = 0;
}
}
/**
* 将指定数据字节写入到此缓冲输出流中
*/
public synchronized void write(int b) throws IOException {
//如果缓冲区已满,先将缓冲区中数据写入输出流
if (count >= buf.length) {
flushBuffer();
}
//将数据b转化为字节写入到缓冲区中
buf[count++] = (byte)b;
}
/**
* 将指定byte数组中从偏移量off开始的len个字节写入此缓冲的输出流。
*
* 1.若写入长度大于缓冲区大小,则先将缓冲区中的数据写入到输出流
* ,然后直接将数组b写入到输出流中
* 2.如果写入长度大于缓冲区中的剩余空间,
* 则先将缓冲区中数据写入输出流,然后将数据写入到缓冲区中
*
* @param b 要写入的数据
* @param off 偏移量
* @param len 写入数据字节的长度
* @exception IOException if an I/O error occurs.
*/
public synchronized void write(byte b[], int off, int len) throws IOException {
//若写入长度大于缓冲区大小,则先将缓冲区中的数据写入到输出流,然后直接将数组b写入到输出流中
if (len >= buf.length) {
flushBuffer();
out.write(b, off, len);
return;
}
//如果写入长度大于缓冲区中的剩余空间,则先将缓冲区中数据写入输出流
if (len > buf.length - count) {
flushBuffer();
}
//将数据写入到缓冲区中
System.arraycopy(b, off, buf, count, len);
//缓冲区有效字节数+len
count += len;
}
/**
* 将缓冲区中数据写入到输出流中
*/
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
}
总结
- BufferedInputStream是缓冲输入流,作用是为另一个输入流添加一些功能,比如缓冲输入功能以及支持mark和reset方法的能力。
- BufferedOutputStream是缓冲输出流,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。