BufferedInputStream 源码——带有缓冲区的装饰器类 BufferedInputStream.class 的UML关系图,如下所示:
BufferedInputStream.class 的UML关系图如下所示BufferedInputStream.class的源码如下所示package java.io; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class BufferedInputStream extends FilterInputStream { // 默认缓冲区byte[]数组大小为8192 字节8KB private static int DEFAULT_BUFFER_SIZE 8192; // 最大缓冲区byte[]数组大小为2147483639byte大约2GB左右 private static int MAX_BUFFER_SIZE Integer.MAX_VALUE - 8; //缓冲区数组用volatile修饰是为了通过AtomicReferenceFieldUpdater进行CAS更新时保证内存的可见性 protected volatile byte buf[]; //底层是通过反射找到目标字段的内存偏移量然后利用Unsafe.class提供的CASCompare-And-Swap操作来原子地更新某个类中指定变量的值 private static final AtomicReferenceFieldUpdaterBufferedInputStream, byte[] bufUpdater AtomicReferenceFieldUpdater.newUpdater (BufferedInputStream.class, byte[].class, buf); //缓冲区byte[]数组中有效字的节数数量 protected int count; //准备从缓冲区中byte[]数组读取的字节索引位置取值范围为0poscount protected int pos; //在缓冲区byte[]数组中标记的某个索引位置-1markpospos //该变量只会在 fill()函数和mark()函数中赋值 protected int markpos -1; // 标记后最多可读取字节数量该变量只会在 mark()函数中赋值 //每当pos-markposmarklimit时就会设置markpos-1 来删除标记 protected int marklimit; //如果被装饰的输入流不为空则返回被装饰的输入Stream该变量在FilterInputStream中定义 private InputStream getInIfOpen() throws IOException { InputStream input in; if (input null) throw new IOException(Stream closed); return input; } //如果缓冲区byte[]数组不为空则返回该缓冲区byte[]数组否则抛出异常 private byte[] getBufIfOpen() throws IOException { byte[] buffer buf; if (buffer null) throw new IOException(Stream closed); return buffer; } //构造函数需要传入一个被装饰的输入Stream, 缓冲区byte[]数组的长度是8192 默认值,8KB public BufferedInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE); } //构造函数需要传入一个被装饰的输入Stream和缓冲区byte[]数组的长度 public BufferedInputStream(InputStream in, int size) { super(in); if (size 0) {//校验缓冲区byte[]数组的长度必须0 throw new IllegalArgumentException(Buffer size 0); } buf new byte[size]; } private void fill() throws IOException { byte[] buffer getBufIfOpen();//获取缓冲区byte[]数组 if (markpos 0)//如果还没有调用过mark()函数那么markpos-1 pos 0;//pos0可以从缓冲区byte[]数组的索引0的位置开始读字节了 else if (pos buffer.length) if (markpos 0) { //场景一pos缓冲区byte[]数组的长度并且markpos 0 int sz pos - markpos; //只把缓冲区byte[]数组中[markpos,pos) 索引区间的元素复制到缓冲区byte[]数组[0,pos-markpos索引区间 System.arraycopy(buffer, markpos, buffer, 0, sz); pos sz;//设置pospos-markpos markpos 0;//设置markpos0 } else if (buffer.length marklimit) {//场景二pos缓冲区byte[]数组的长度并且缓冲区byte[]数组的长度 marklimit markpos -1; //设置markpos -1 pos 0; //设置pos 0 } else if (buffer.length MAX_BUFFER_SIZE) {//场景三pos缓冲区byte[]数组的长度并且缓冲区byte[]数组的长度 2147483639 throw new OutOfMemoryError(Required array size too large); } else {//场景四pos缓冲区byte[]数组的长度并且不满足场景一、二、三时将缓冲区byte[]数组扩容 //如果pos2147483639/2则新缓冲区byte[]数组的长度nszpos*2否则新缓冲区byte[]数组的长度nsz2147483639 int nsz (pos MAX_BUFFER_SIZE - pos) ? pos * 2 : MAX_BUFFER_SIZE; if (nsz marklimit) nsz marklimit;//当新缓冲区byte[]数组的长度nszmarklimit新缓冲区byte[]数组的长度nszmarklimit byte nbuf[] new byte[nsz];//新建一个新缓冲区byte[]数组 System.arraycopy(buffer, 0, nbuf, 0, pos);//将老缓冲区byte[]数组中[0,pos)索引区间的元素复制到新缓冲区byte[]数组[0,pos)索引区间 if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {//通过CASCompare-And-Swap操作来原子地更新buf变量 // Cant 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;//新缓冲区byte[]数组创建完毕 } count pos; //将被装饰的输入Stream中的字节读取到缓冲区byte[]数组的[pos,buffer.length - pos)索引位置并返回读取的字节数量 int n getInIfOpen().read(buffer, pos, buffer.length - pos); if (n 0) count n pos;//count从被装饰的输入Stream中读取的字节数量pos } //线程同步的函数从缓冲区byte[]数组中读取1个字节 public synchronized int read() throws IOException { //poscount有2种情况pos不可能count //场景一poscount0缓冲区byte[]数组还没有填充任何数据。 //场景二poscount≠0缓冲区byte[]数组中的数据已经通过pos读取完了。 if (pos count) { fill();//符合场景一或场景二都会调用fill()函数 if (pos count) return -1;//如果调用了fill()函数后仍然符合场景一或场景二表示被装饰的输入Stream已经读完了返回-1 } //执行到这里时表明pos count返回缓冲区byte[]数组pos索引位置的字节; return getBufIfOpen()[pos] 0xff; } //从缓冲区byte[]数组或被装饰的输入Stream中读取len个字节到指定的byte[]数组b中这len个字节被放到byte[]数组b的[off,offlen)索引位置。 //该函数只被read()函数调用 private int read1(byte[] b, int off, int len) throws IOException { //只在fill()函数中修改count变量的值count变量的值只有以下2种可能 //①、countpos②、count从被装饰的输入Stream中读取的字节数量pos //因此avail表示缓冲区byte[]数组中[pos,poscount)索引位置的字节数量 int avail count - pos; if (avail 0) {//缓冲区byte[]数组中[pos,poscount)索引位置的字节数量0其实不可能0只可能0 //要读取的len个字节缓冲区byte[]数组的长度同时markpos -1 if (len getBufIfOpen().length markpos 0) { return getInIfOpen().read(b, off, len);//从被装饰的输入Stream中读取len个字节到指定的byte[]数组b中这len个字节被放到byte[]数组b的[off,offlen)索引位置。 } fill();//调用fill()函数 avail count - pos;//重新计算avail if (avail 0) return -1;//如果avail仍然0返回-1 } int cnt (avail len) ? avail : len;//此时avail0取avail和len中较小的值作为本次从缓冲区byte[]数组中读取的字节数量 System.arraycopy(getBufIfOpen(), pos, b, off, cnt);//从缓冲区byte[]数组的pos索引开始读取avail或len2者取其小个字节到指定的byte[]数组b的[off,offcnt)索引位置cnt就是avail或len中2者取其小的值 pos cnt;//pos向前移动avail或len2者取其小个索引位置 return cnt;//返回avail或len2者取其小 } //线程同步的函数从缓冲区byte[]数组中读取len个字节到指定的byte[]数组b中这len个字节被放到byte[]数组b的[off,offlen)索引位置。 public synchronized int read(byte b[], int off, int len) throws IOException { getBufIfOpen(); //检测被装饰的输入Stream是否关闭 if ((off | len | (off len) | (b.length - (off len))) 0) {//相当于off len b.length这样写代码的好处我没看出来 throw new IndexOutOfBoundsException(); } else if (len 0) { return 0;//要从缓冲区byte[]数组中读取的len个字节0时返回0 } int n 0;//累计从缓冲区byte[]数组或被装饰的输入Stream中读取的字节数量 for (;;) {//循环调用read1()函数完成从缓冲区byte[]数组或被装饰的输入Stream中读取len个字节到指定的byte[]数组b中这len个字节被放到byte[]数组b的[off,offlen)索引位置。 int nread read1(b, off n, len - n);//nread用来统计每次从read1()函数中读取一定的字节数量并放到byte[]数组b的[off,offlen)索引位置。 if (nread 0) return (n 0) ? nread : n;//当read1()函数返回0或者-1时表示缓冲区byte[]数组中和被装饰的输入Stream中已经没有可以读取的字节了 n nread;//累计从缓冲区byte[]数组或被装饰的输入Stream中读取的字节数量 if (n len)//累计从缓冲区byte[]数组或被装饰的输入Stream中读取的字节数量总和len时其实不可能len只可能len return n;//返回n // 被装饰的输入Stream中已经没有字节可以用了返回累计从缓冲区byte[]数组或被装饰的输入Stream中读取的字节数量总和 InputStream input in; if (input ! null input.available() 0) return n; } } //线程同步的函数从缓冲区byte[]数组中跳过了n个字节 public synchronized long skip(long n) throws IOException { getBufIfOpen(); //检测被装饰的输入Stream是否关闭 if (n 0) { return 0; } //只在fill()函数中修改count变量的值count变量的值只有以下2种可能 //①、countpos②、count从被装饰的输入Stream中读取的字节数量pos //因此avail表示缓冲区byte[]数组中[pos,poscount)索引位置的字节数量 long avail count - pos; if (avail 0) {//缓冲区byte[]数组中[pos,poscount)索引位置的字节数量0其实不可能0只可能0 // If no mark position set then dont keep in buffer if (markpos 0)//同时markpos0 return getInIfOpen().skip(n);//调用被装饰的输入Stream的skip()函数 // Fill in buffer to save bytes for reset fill();//调用fill()函数跟read1()函数中的操作一样 avail count - pos;//重新计算avail跟read1()函数中的操作一样 if (avail 0) return 0;//如果avail仍然0返回-1 } long skipped (avail n) ? avail : n;//此时avail0取avail和n中较小的值作为本次从缓冲区byte[]数组中跳过的字节数量 pos skipped;//pos向前移动avail或n2者取其小个索引位置与read1()函数异曲同工表示本次从缓冲区byte[]数组中跳过了skipped个字节 return skipped;//返回本次从缓冲区byte[]数组中跳过的skipped个字节 } //线程同步的函数计算缓冲区byte[]数组的最大长度或者叫容量 public synchronized int available() throws IOException { //只在fill()函数中修改count变量的值count变量的值只有以下2种可能 //①、countpos②、count从被装饰的输入Stream中读取的字节数量pos //因此count - pos表示缓冲区byte[]数组中[pos,poscount)索引位置的字节数量 int n count - pos; int avail getInIfOpen().available();//调用被装饰的输入Stream的available()函数返回被装饰的输入Stream中仍然可以读取的字节数量 return n (Integer.MAX_VALUE - avail)//(缓冲区byte[]数组中[pos,poscount)索引位置的字节数量 被装饰的输入Stream中仍然可以读取的字节数量) 2147483647时返回2147483647表示缓冲区byte[]数组的最大容量为2147483647 ? Integer.MAX_VALUE : n avail;//返回(缓冲区byte[]数组中[pos,poscount)索引位置的字节数量 被装饰的输入Stream中仍然可以读取的字节数量)表示缓冲区byte[]数组的最大容量为该数量 } //线程同步的函数给marklimit和 markpos赋值或者叫标记 marklimit和 markpos public synchronized void mark(int readlimit) { marklimit readlimit; markpos pos; } //线程同步的函数pos markpos或者叫对齐pos索引位置 到markpos索引位置 public synchronized void reset() throws IOException { getBufIfOpen(); //检测被装饰的输入Stream是否关闭 if (markpos 0) throw new IOException(Resetting to invalid mark); pos markpos; } //返回当前这个class是否支持mark()函数和 reset()函数 public boolean markSupported() { return true; } //关闭被装饰的输入Stream释放缓冲区byte[]数组让gc回收。 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() } }