BufferedInputStream 是带有缓存区的输入字节流,使用byte[ ] 数组来进行缓存。当需要从外界读入数据到内存中时,先一次性读取多个字节的数据存放到byte[ ] 数组当中。之后对数据的存取都是操作byte[ ] 数组这个缓存区,从而提高了性能。
BufferedInputStream类结构如下所示:
在java的io模块中经常看到装饰器模式的身影,FilterInputStream 利用装饰器模式将InputStream 封装成类的成员变量,可以看到FilterInputStream 类其实并没有做什么特殊处理而是将所有操作全都委托 in 对象处理。
class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
public int read() throws IOException {
return in.read();
}
... ...
}
BufferedInputStream 继承 FilterInputStream ,其成员变量如下所示:
public class BufferedInputStream extends FilterInputStream {
//默认的缓存区大小
private static int DEFAULT_BUFFER_SIZE = 8192;
//最大缓冲区大小
private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
//缓存区
protected volatile byte buf[];
//缓存区可读字节总数
protected int count;
//缓存区当前可读位置
protected int pos;
//标记位
protected int markpos = -1;
//在读取marklimit个字节后,markpos标记会失效
protected int marklimit;
。。。 。。。
}
BufferedInputStream 类的mark(int readlimit) 是在缓存区的当前pos位置设置一个markpos标记,后续读取readlimit(marklimit)个字节之前,markpos 标记依然有效。markpos 标记有效时,可以调用reset 方法将pos重置到markpos位置,这样就可以回头重复读取之前读取过的数据。
BufferedInputStream 类的read(byte b[], int off, int len) ,从byte[] b的off索引位置开始将长度为len个字节的数据读入到byte[] b 中:
其核心操作是:1、前置校验 2、如果缓冲区没有可读字节,操作inputStream对象从新填充缓冲区 3、读取缓冲区
public synchronized int read(byte b[], int off, int len)
throws IOException
{
//检查流是否被关闭
getBufIfOpen(); // Check for closed stream
//如果需要读取的长度大于b的长度则抛出异常
//如果需要读取的长度为0,直接返回0
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
//n表示实际读取的字节数
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;
}
}
private int read1(byte[] b, int off, int len) throws IOException {
//avail 剩余可读取的字节数
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. */
//如果需要读取的字节数大于缓冲区的长度,没有设置标记位
//就直接调用InputStram in 对象读入数据,不使用缓冲区
if (len >= getBufIfOpen().length && markpos < 0) {
return getInIfOpen().read(b, off, len);
}
//填充缓冲区,操作InputStram in 对象读入数据到缓冲区
fill();
//重置缓冲区剩余可读字节数
avail = count - pos;
if (avail <= 0) return -1;
}
int cnt = (avail < len) ? avail : len;
//从缓冲区读取数据到byte[] b,并返回实际读取的字节数
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
pos += cnt;
return cnt;
}
这里最重要的方式是fill(),它调用InputStream in 对象一次性读入多个字节的数据存放到缓冲区。什么时候需要填充缓冲区呢?随着数据的读取pos会变大,当pos>=count时,说明缓冲区已经没用剩余可读数据了,需要调用 in 对象读入数据覆盖缓冲区,同时重置pos和count值。前面我们还提到了markpos,为了能够重复读取之前的数据,缓冲区需要保留markpos到marklimit 之间的数据。因此在我们覆盖缓冲区时,就必须要考虑到这个情况。
1、当没有设置markpos
当没有设置markpos,就不需要保留可重复读的数据,只需要将pos设置为0,再根据新读入的字节数重置count位置,这样就重置了缓冲区。
2、当设置了markpos,这里又要分几种情况:
A:markpos >0 ,pos>=buffer.length,这说明当前缓冲区已经没有空余可读空间了,
此时当markpos > 0 时 那么只需要丢弃markpos之前的数据,保留markpos 到pos之间的数据,count,pos = post-markpos, markpos=0。调整后pos到buffer.length之间就是可以填充数据的空间
B:当markpos=0,pos>=buffer.length,buffer.length >= marklimit 时 可以得出在markpos标记后已经读取了超过marklimit 个字节,此时markpos已失效,直接将pos=0重置缓冲区,无需保留额外数据
C:当marpos=0,buffer.length < marklimit,说明整个缓冲区都需要被保留,因此只能扩大缓冲区容量。
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
//没有设置标记对应情况1
pos = 0; /* no mark: throw away the buffer */
else if (pos >= buffer.length) /* no room left in buffer */
对应情况2-A
if (markpos > 0) { /* can throw away early part of the buffer */
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
对应情况2-B
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else if (buffer.length >= MAX_BUFFER_SIZE) {
throw new OutOfMemoryError("Required array size too large");
} else { /* grow buffer */
对应情况2-C
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);
将buffer缓冲区引用原子的设置为nbuf
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
// Can't replace buf if there was an async close.
// Note: This would need to be changed if fill()
// is ever made accessible to multiple threads.
// But for now, the only way CAS can fail is via close.
// assert buf == null;
throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
//调用in对象填充缓冲区
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
参考:https://blog.csdn.net/songwei128/article/details/23355045
https://blog.csdn.net/weixin_33862188/article/details/85631788