BufferedInputStream :存在缓冲器的输入流,并提供mark和reset方法,在 BufferedInputStream构造器初始化时,会创建一个缓冲数组。mark方法标记输入流的当前位置点,reset方法会从最近一次的mark点重新读取流数据。
BufferedInputStream 源码:
public class BufferedInputStream extends FilterInputStream {
//存储数据的缓冲数组;必要是可以被另外一个不同大小的数组替换;volatile表明是多线程即时可见
protected volatile byte buf[];
//缓冲数组的原子更新器,针对volatile修饰的buf数组
private static final AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater(BufferedInputStream.class, byte[].class, "buf");
//缓冲数组中有效的字节数,buf[0]和buf[count-1]存储了缓冲的数据流
protected int count;
//缓冲数组的当前读取位置,值在0和count之间
protected int pos;
//最后一次mark方法调用后,存储的标记位置,值在-1和pos之间;当markpos!=-1,则从buf[markpos]至buf[pos-1]之间的值均在缓冲数组中保留,如果markpos=0,在pos读到缓冲数组末尾时,如果buffer的长度超过了marklimit则此markpos标记会被抛弃
protected int markpos = -1;
//保留标记位置的最大区间,buffer的长度大于marklimit,标记位置不再保留。
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;
}
//构造函数,defaultBufferSize默认大小为8192
public BufferedInputStream(InputStream in) {
this(in, defaultBufferSize);
}
//构造函数,同时创建一个大小为size的缓冲数组
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
/*用数据来填充缓存数组,填充时规则:
1 如果没有标记位置(mark<0),则可以将缓冲数组中的数据都舍弃(throw away),将pos=0,以备继续读取底层流数据,覆盖原数据(全部覆盖还是只覆盖数组前面一部分,取决于还有多少流数据要被读取);
2 如果有标记位置(markpos>=0),且pos<buffer.length时,表明buffer中有空间,则可以继续读取流数据
3 如果有标记位置(markpos>=0),且pos>=buffer.length时,表明已经读到缓冲数组的末尾。
1) 如果markpos>0,也就是说此时标记位置markpos到pos之间的数据需要保存(以便reset),而缓冲数组中markpos之前的数据可以舍弃。将缓冲数组中需要保留的数据复制到此数组前面sz=pos-markpos的空间中,pos为sz,markpos=0为起始位置。然后可以从底层数据流中继续缓冲进buffer中大小为length-pos的数据。count为实际读取的字节大小+pos.
2) 如果markpos=0,且buffer的长度大于marklimit,则标记位置不再保留,pos=0,此时将缓冲数组的数据舍弃,继续读取底层流数据(即走规则1),当调用reset方法时,此时会报异常(Resettin to invalid mark)
3)如果markpos=0,且buffer.length<=marklimit,则对buffer扩容,buffer的大小扩展为pos*2(当pos*2<marklimit时;如果pos*2>marklimit,则buffer的扩容后的大小为marklimit),并将原buffer中保存的数据拷贝到新buffer中,继续读取底层流数据。
注:通过修改defaultBufferSize的大小,本地可以重现以上各种规则下的fill操作
*/
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; /* no mark: throw away the buffer 没有标记位置时,将pos定位到缓冲数组的起始位置 */
else if (pos >= buffer.length) /* no room left in buffer */
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) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else { /* grow buffer */
int nsz = pos * 2;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
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;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
//返回当前位置+1的byte值(int表示)。
//byte[i] & 0xff:此操作是将byte数据转成int型;使用了这个操作,最终的整形数据只有低8位有数据,其他位数都为0。
(0xff二进制一共32位,后8位是1,其他位为0)
public synchronized int read() throws IOException {
if (pos >= count) {//判断当前位置是否>=count值,如果是,则进行fill操作,填充缓冲数组
fill();
if (pos >= count)
return -1;
}
//如果pos小于count值,则返回数组当前索引的值
return getBufIfOpen()[pos++] & 0xff;
}
}
补充其他方法如下:
/*read1方法,此方法为私有方法:
* 判断缓冲数组是否可读,
* 1、如果可读,从缓冲数组中读取cnt长度,cnt是可读长度与欲读长度的较小者。
* 2、如果avail<=0,即缓冲数组中没有可读字节,判断len是否大于缓冲数组长度且无mark标记,
* 如果是,则直接从底层流读取数据,并返回
* 如果不是,则fill缓冲数组,继续判断是否可读,如果可读走流程1,不可读则已到达流末尾,直接返回-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) {
return getInIfOpen().read(b, off, len);
}
fill();
avail = count - pos;
if (avail <= 0) return -1;
}
int cnt = (avail < len) ? avail : len;
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
pos += cnt;
return cnt;
}
/*此方法尽可能的读取数据,直到达到欲读长度len,或流的末尾,才停止。通过调用read1方法实现。
* 根据条件判断是否达到欲读长度len或流的末尾来一次或多次调用read1方法
*/
public synchronized int read(byte b[], int off, int len)
throws IOException
{
getBufIfOpen(); // Check for closed stream
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;
}
}
/* 略过多少字节
* 返回实际略过的字节数
*/
public synchronized long skip(long n) throws IOException {
getBufIfOpen(); // Check for closed stream
if (n <= 0) {
return 0;
}
long avail = count - pos;
if (avail <= 0) {
// If no mark position set then don't keep in buffer
if (markpos <0)
return getInIfOpen().skip(n);
// Fill in buffer to save bytes for reset
fill();
avail = count - pos;
if (avail <= 0)
return 0;
}
long skipped = (avail < n) ? avail : n;
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;
}
// mark方法,标记当前位置
public synchronized void mark(int readlimit) {
marklimit = readlimit;
markpos = pos;
}
//reset方法:重置当前位置为markpos
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方法
public boolean markSupported() {
return true;
}
//关闭流,并释放缓冲数组中的数据
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()
}
}