类的继承和包的导入
BufferedInputStream类引用了一个其他类
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
同时BufferedInputStream类本身继承FilterInputStream
(以上类在后续文章中讲到后会贴链接,本篇文章不做过多解释)
理解一个类的作用最方便快捷的的方式就是查看其类开头的注释:
/** * A <code>BufferedInputStream</code> adds * functionality to another input stream-namely, * the ability to buffer the input and to * support the <code>mark</code> and <code>reset</code> * methods. When the <code>BufferedInputStream</code> * is created, an internal buffer array is * created. As bytes from the stream are read * or skipped, the internal buffer is refilled * as necessary from the contained input stream, * many bytes at a time. The <code>mark</code> * operation remembers a point in the input * stream and the <code>reset</code> operation * causes all the bytes read since the most * recent <code>mark</code> operation to be * reread before new bytes are taken from * the contained input stream. * * @author Arthur van Hoff * @since JDK1.0 */
大意如下:
BufferedInputStream类是对其他输入流添加了一个缓冲读入的功能,并且拥有“标记”和“重置”方法。
当创建一个BufferedInputStream实例时,会创建一个内置的缓冲数组。
从输入流中读取字节或跳过字节时(字节可为复数),内部缓冲区从其所控制的输入流中再次读入字节是必要的,可一次性读取多个字节。
“标记”操作是在输入流(缓冲区)中设置记忆点,“重置”操作则是从最近的“标记”点开始重新读取(类似sql的回滚)。
在BufferedInputStream中声明了八个变量及对象:
默认的缓冲区大小
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<BufferedInputStream, byte[]> bufUpdater = AtomicReferenceFieldUpdater.newUpdater (BufferedInputStream.class, byte[].class, "buf");
当前缓冲区的有效字节数
protected int count;
当前缓冲区的读取位置
protected int pos;
当前缓冲区的标记位置(-1为空)
protected int 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; }
构造函数(创建一个默认缓冲空间的实例)
public BufferedInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE); }
构造函数(创建一个自定义大小缓冲空间的实例)
public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; }
从输入流装填入缓冲区,首先判断是否存在标记位,如果不存在标记位先将当前位置指向0,然后重置有效长度位(置为0),从输入流中读满数据,然后将有效长度位置于数组末尾。若不存在标记则分五种情况考虑,当当前位置大于超出数组范围时,有四种情况:标记位非数组头、数组长度大于标记最大值、数组长度大于最大缓冲区大小和其他情况。标记位非数组头时,将标记位移至数组头,当前位置相对标记位置同样移动,然后读满缓冲区(同上);当数组长度大于标记最大值时,将标记位置空,当前位置置0,然后读满缓冲区;当数组长度大于最大长度时,抛出内存溢出;其他情况时,设置变量nsz为当前位置的2倍和最大长度之中的较小值,当nsz大于标记最大值,nsz指向标记最大值,将当前的缓冲数组头到当前位置的数据拷贝至大小为标记最大值的新数组中,将buffer设为新数组的引用。最后一种情况是直接填充缓冲区即可。
private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; /* no mark: throw away the buffer */ 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 if (buffer.length >= MAX_BUFFER_SIZE) { throw new OutOfMemoryError("Required array size too large"); } else { /* 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)) { // 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; }
读取缓冲区中数据,返回buf中第pos位的二进制码,并且pos位前移。如果pos位超过缓冲区的有效数据位,则进行缓冲区装填
public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; }
将缓冲数组复制到新的字节数组数组中(off为起始位置,len为写入长度),返回值为实际拷贝的长度。如果当前位置超出缓冲区有效位置,进一层if。在一层if下若需拷贝长度大于缓冲数组长度且不存在标记点时,直接从输入流中拷贝数据并返回,如果不满足条件则进行缓冲区装填,并重新判断当前位置与有效位置的位置关系,如果标记点仍在有效范围外,则程序错误,返回-1,否则出一层if。判断缓冲区的实际有效长度(有效标记点到当前位置的长度)和len的大小,写入较小值cnt的位数,pos前移cnt位并返回cnt。
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; }
使用逻辑同上。工作流程:首先判定流是否关闭(buf是否被初始化),若off或len或off+len或目标数组长度与off+len的差值小于0则抛出数组越界错误,或者当需要拷贝的长度为0时直接返回。接下来设置一个判定位n,进循环。首先执行一次read1函数,判定read1的返回值是否小于等于0(即实际拷贝的长度是否为非正),如若小于等于0,进行三元运算,n为0是返回read1的返回值,n不为0则返回n,结束判定。修改n的数值,n为每次read1执行的累加和,判定若n大于需拷贝长度,直接返回n。开启新输入流(实际仍为本实例操控的输入流),若改输入流不为空且流中下个字节不可读则返回n。
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; }
设置标记,记录当前位置
public synchronized void mark(int readlimit) { marklimit = readlimit; markpos = pos; }
数据回滚,回滚至上个mark点开始读取
public synchronized void reset() throws IOException { getBufIfOpen(); // Cause exception if closed if (markpos < 0) throw new IOException("Resetting to invalid mark"); pos = markpos; }
恒真判定,表示一种属性:该类对象支持mark操作
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() } }
BufferedInputStream类大体结构如下:上接InputStream,中间为缓冲区,下部对接其他对象或方法使其可以从缓冲区中读取数据。
作为较常使用的流读入类,熟悉BufferedInputStream是必要的。
需要注意java在函数返回值时返回的是引用,在数组直接‘=’时也是引用