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[];
/**
* 不太懂
*/
private static final
AtomicReferenceFieldUpdater bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf");
/**
* 缓冲区中的有效字节数目,在0-buf.length之间
*/
protected int count;
/**
*/buf索引,也即指示下一个读取的字节。*/
protected int pos;
/** 最多能mark的字节长度,也就是从mark位置到当前pos的最大长度。
* 以pos表示最后一次调用mark方法时buf缓冲区的索引值
* 1. 当调用mark方法时,记录pos的值,取值范围[-1,pos]。
* 2. 如果无mark操作,那么它的值为-1,
* 3. 如果有mark操作,那么当调用reset之后,buf[markpos]成为第一个将被读取的字节
* 4. 如果marpos不是-1, 那么在buf[markpos]到buf[pos-1]之间的字节将被保留在缓冲区中,直到pos和markpos之间的差大于marklimit才会被丢弃
*/
protected int markpos = -1;
/** reset方法失效前允许读取的最大字节数。
* 即pos - markpos > marklimit时,标记markpos将被重新设为-1
*/
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;
}
/**
* 创建BufferedInputStream对象,缓冲区大小为默认值。
*/
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
/**
*创建BufferedInputStream对象,缓冲区大小为size且size>0。
*/
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
/**
* 从包含的输入流中读取一些新数据放入缓冲内存中,之后直到缓冲内存中的数据读完前都不会再从真正的流中读取数据。
* 可能需要扩展缓冲区大小。
* 假设正在被同步的方法访问。这个方法还假设所有的数据都被读取,因此pos > count
*/
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; //未执行mark操作,直接抛弃已有内容,从输入流重新读取。
else if (pos >= buffer.length) /*缓冲区填满且markpos>=0 */
if (markpos > 0) {
int sz = pos - markpos;
//复制[markpos,pos-1]之间的内容到[0,sz-1]
System.arraycopy(buffer, markpos, buffer, 0, sz);
//重置pos和markpos
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
/*markpos=0时且buff.length>=marklimit ,权且记住这么处理吧!
*/
markpos = -1;
pos = 0;
} else if (buffer.length >= MAX_BUFFER_SIZE) {
//超出最大缓存容量,无法分配多余的空间
throw new OutOfMemoryError("Required array size too large");
} else { /* grow buffer */
/**条件:
* 缓冲区满了pos >= buffer.length
* markpos==0(有标记位)
* buffer.length < MAX_BUFFER_SIZE(容量可以扩展)
* buffer.length < marklimit(reset有效长度)
* 这一操作中未发生变化的值:pos, marklimit, markpos
*/
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
//nsz取marklimit与nsz中的最小值,如果marklimit是个比较合理的数字,将容量扩至marklimit大小。
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并从包含的输入流输入读取数据,将buffer填满。
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
public synchronized int read() throws IOException {
if (pos >= count) { //当pos>=count的时候也就是表示当前的byte中的数据为空或已经被读完
fill();
if (pos >= count)
return -1;
}
/**为何使用int作返回类型?以及使用与操作?
从取值范围来看int包含了char和byte,这为使用int作为返回值类型提供了可能。
在应用中我们一般用read()接口的返回值是-1则表示已经读到文件尾(EOF)。
char的取值范围本身不包含负数,所有用int的-1表示文件读完没问题,
但byte的取值范围[-128,127],包含了-1,读取的有效数据范围就是[-128,127],
没办法用这个取值范围中的任何一个数字表示异常或者数据已经读完,
所以接口如果直接使用byte作为返回值不可行,直接将byte强制类型转换成int也不行,
因为如果读到一个byte的-1,转为int了也是-1,会被理解为文件已经读完。
所以这里做了一个特殊处理return getBufIfOpen()[pos++] & 0xff。
*
*/
return getBufIfOpen()[pos++] & 0xff;
}
/**
* Read characters into a portion of an array, reading from the underlying
* stream at most once if necessary.
*/
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;
}
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;
}
/**
* Returns an estimate of the number of bytes that can be read (or
* skipped over) from this input stream without blocking by the next
* invocation of a method for this input stream. The next invocation might be
* the same thread or another thread. A single read or skip of this
* many bytes will not block, but may read or skip fewer bytes.
*
* This method returns the sum of the number of bytes remaining to be read in
* the buffer (count - pos
) and the result of calling the
* {@link java.io.FilterInputStream#in in}.available().
*
* @return an estimate of the number of bytes that can be read (or skipped
* over) from this input stream without blocking.
* @exception IOException if this input stream has been closed by
* invoking its {@link #close()} method,
* or an I/O error occurs.
*/
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以后最多可以读取readlimit字节数据,mark标记仍有效。
*/
public synchronized void mark(int readlimit) {
marklimit = readlimit;
markpos = pos;
}
public synchronized void reset() throws IOException {
getBufIfOpen(); // Cause exception if closed
if (markpos < 0)
throw new IOException("Resetting to invalid mark");
pos = markpos;
}
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()
}
}
}
在执行mark操作时,需指定一个有效的readlimit参数,以说明在至多读取多少个字节后reset操作仍然合法。
参数的意义同源码。