@author ixenos
概念
BufferedInput/OutputStream是实现缓存的过滤流,他们分别是FilterInput/OutputStream的子类。
BufferedInputStream工作流程
stream-->buf--read buf-->I
1.当一个BufferedInputStream被创建时,一个内部的缓冲区 byte[] buf = new byte[size] 也被建立,size默认是8192也就是默认创建一个8kb大小的缓存空间,
工作时由内部方法fill()预先在缓冲区存储来自连接输入流的数据;最终的数据来源由底层流决定,比如new FileInputStream("a.txt")来源就是a.txt,而我们过滤流的数据来源是节点流!
1 private static int DEFAULT_BUFFER_SIZE = 8192; //默认缓冲数组长度大小,折合单位有8kb 2 protected volatile byte buf[]; //底层维护的缓冲数组变量 3 4 public BufferedInputStream(InputStream in) { 5 this(in, DEFAULT_BUFFER_SIZE); //默认8192 6 } 7 8 9 public BufferedInputStream(InputStream in, int size) { 10 super(in); //引用基类FilterInputStream的构造方法,基类有底层流变量 11 if (size <= 0) { 12 throw new IllegalArgumentException("Buffer size <= 0"); 13 } 14 buf = new byte[size]; //初始化缓冲数组,指定大小为size 15 }
2.当BufferedInputStream的read方法被调用时,数据将从缓冲区中移出,而不是底层的输入流;
3.当BufferedInputStream缓冲区数据用完时,他自动从底层输入流中补充数据。
read()源码分析:
//返回下一个数据字节,如果到达流末尾则返回-1,数据流向返回值!
1 public synchronized int read() throws IOException { 2 if (pos >= count) { //当缓存指针等于或者超过count时,说明buf已满 3 fill(); //fill()从节点流中读取数据存放到buf缓冲区 4 if (pos >= count) 5 return -1; 6 } 7 return getBufIfOpen()[pos++] & 0xff; //得到非空buf缓冲当前索引为pos位置的一字节数据,pos++ 缓冲区指针移动一位 8 } 9 10 //getBufIfOpen对buf进行非空判断 11 private byte[] getBufIfOpen() throws IOException { 12 byte[] buffer = buf; //用一个引用变量引用buf 13 if (buffer == null) 14 throw new IOException("Stream closed"); 15 return buffer; //返回buffer,实质上指向buf 16 }
read(byte[], int, int)源码分析 :
//pos是起始/下一次读取buffer的位置,
//markpos是最新一次已读取的位置,
//count是缓冲数组被填充的大小-1(因为是作为index存在的,从0开始)
//len是b要读取的最大字节数
1 //返回读取的字节数,如果流到达末尾返回-1,数据流向b数组! 2 public synchronized int read(byte b[], int off, int len) 3 throws IOException 4 { 5 6 /*这里只做检查,流关闭则提前抛出,以避免拖到fill()中的getBufIfOpen()再抛出的情况 7 private byte[] getBufIfOpen() throws IOException { 8 byte[] buffer = buf; //引用传递,指向当前buf变量指向的内存 9 if (buffer == null) //就为了这个非空判断而已 10 throw new IOException("Stream closed"); 11 return buffer; //这里返回值是没人接收的,方法结束抛弃局部变量buffer,不对buf造成影响 12 } 13 */ 14 getBufIfOpen(); // Check for closed stream 关闭流将抛出异常 15 //指定数组的越界判断 16 if ((off | len | (off + len) | (b.length - (off + len))) < 0) { 17 throw new IndexOutOfBoundsException(); 18 } else if (len == 0) { 19 return 0; 20 } 21 22 int n = 0; 23 //循环写数据到b,直到buffer数据不足返回-1,此时nread已累加记录 24 for (;;) { 25 int nread = read1(b, off + n, len - n);//读缓冲区数据到b 26 if (nread <= 0) 27 return (n == 0) ? nread : n; 28 n += nread; 29 //读完指定长度时返回 30 if (n >= len) 31 return n; 32 // if not closed but no bytes available, return 33 //没读完指定长度,但缓冲区没数据时返回当前n 34 InputStream input = in; 35 if (input != null && input.available() <= 0) 36 return n; 37 } 38 } 39 40 41 /** 42 * read1方法是该read底层用来读取缓冲区buf的数据进指定数组的方法 43 * 而fill方法又是read1方法中用来读取底层字节流到缓冲区buf的方法 44 * 读取数据写进指定数组的一部分,必要时每次从底层流尽量读取数据 45 */ 46 private int read1(byte[] b, int off, int len) throws IOException { 47 int avail = count - pos; //开始时为0 48 if (avail <= 0) { 49 /* 如果所请求的长度大于等于缓冲区,并且还没读取过buf
50 (即刚开始使用时,扩充缓冲区成本不值得,回归底层流), 51 就没必要用缓冲区了,将直接从底层流读取数据。
52 */ 53 if (len >= getBufIfOpen().length && markpos < 0) { 54 //直接调用底层流的read方法,写进指定的内存b 55 return getInIfOpen().read(b, off, len); 56 } 57 //请求fill()方法来读取底层流数据(多次使用后如果长度大于缓冲区,为了数据保护,将扩充缓冲区) 58 fill(); 59 avail = count - pos; //avail是 60 if (avail <= 0) return -1; 61 } 62 int cnt = (avail < len) ? avail : len; //cnt是要写入b的字节数,如果b中剩余字节数avail比要刷入的数据长度len大,则cnt为len 63 System.arraycopy(getBufIfOpen(), pos, b, off, cnt); //复制指定范围非空buf数据到b 64 pos += cnt; 65 return cnt; 66 } 67 68 69 //fill()方法填充空间,是底层用来读取流数据到缓冲区buf 70 /** 71 * Fills the buffer with more data, taking into account 72 * shuffling and other tricks for dealing with marks. 73 * Assumes that it is being called by a synchronized method. 74 * This method also assumes that all data has already been read in, 75 * hence pos > count. 76 */ 77 private void fill() throws IOException { 78 byte[] buffer = getBufIfOpen(); 79 //pos是起始/下一次读取buffer的位置,markpos是最新一次已读取的位置,count是缓冲数组被填充的大小-1(因为是作为index存在的,从0开始) 80 //起始小于零表明没有pos移动,从源码可知 81 if (markpos < 0) 82 pos = 0; /* no mark: throw away the buffer */ 83 //当pos比buffer更长时,即 84 else if (pos >= buffer.length) /* no room left in buffer */ 85 //markpos也有前移时,数组自我复制丢弃早期部分 86 if (markpos > 0) { /* can throw away early part of the buffer */ 87 int sz = pos - markpos; 88 System.arraycopy(buffer, markpos, buffer, 0, sz); 89 pos = sz; 90 markpos = 0; 91 //buffer大小超过理论规模时,重置,通过改变pos“丢弃”缓冲区内容 92 } else if (buffer.length >= marklimit) { 93 markpos = -1; /* buffer got too big, invalidate mark */ 94 pos = 0; /* drop buffer contents */ 95 //buffer大小超过本地VM内存限制:MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8 96 } else if (buffer.length >= MAX_BUFFER_SIZE) { 97 throw new OutOfMemoryError("Required array size too large"); 98 } else { /* grow buffer */ //没有顾忌时,扩充缓冲区大小 99 int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? 100 pos * 2 : MAX_BUFFER_SIZE; 101 if (nsz > marklimit) 102 nsz = marklimit; 103 byte nbuf[] = new byte[nsz]; 104 System.arraycopy(buffer, 0, nbuf, 0, pos); 105 /*AtomicReferenceFieldUpdater是一个基于反射的工具类, 106 它能对指定类的指定的volatile引用字段进行原子更新。 107 (注意这个字段不能是private的) , 108 从源码知getBufIfOpen返回的是值传递的protected volatile byte buf[] 109 */ 110 if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { 111 // Can't replace buf if there was an async close. 112 // Note: This would need to be changed if fill() 113 // is ever made accessible to multiple threads. 114 // But for now, the only way CAS can fail is via close. 115 // assert buf == null; 116 throw new IOException("Stream closed"); 117 } 118 buffer = nbuf; 119 } 120 //pos小于buf.length时,从底层流到缓冲区,使用InputStream的read(byte[])方法 121 count = pos; 122 int n = getInIfOpen().read(buffer, pos, buffer.length - pos); 123 if (n > 0) 124 count = n + pos; 125 } 126
总结:通过以上源码我们可以看到:
1)read(byte[])方法运行初始就用getBufIfOpen判断流关闭没有,因为缓冲流关闭后缓冲区失效,getBufIfOpen将抛出异常;
2)read(byte[])方法中的read1方法是该read底层,用来读取缓冲区buf的数据进指定数组的方法,而fill方法又是read1方法中,用来读取底层字节流到缓冲区buf的方法,层层封装外包;
3)read(byte[]) <-- read1(byte[]) <-- fill() <-- getInIfOpen().read(buf) <-- InputStream 层层外包,
这里getInIfOpen()返回一个非空底层流InputStream,并调用底层流的read(byte[])方法读取流数据到缓冲区buf,
所以当我们只是关闭底层流这些嵌套流也跟着“关闭”,但是会留下缓冲数组buf,这有时可以利用,有时要注意关闭避免浪费资源;
以下是close()的源码分析:
1 1 public void close() throws IOException { 2 2 byte[] buffer; 3 3 while ( (buffer = buf) != null) { 4 4 if (bufUpdater.compareAndSet(this, buffer, null)) { // 会将缓冲区buf的数据清空并释放缓冲区,也就是buf=null 5 5 InputStream input = in; //引用传递,input和in同指向节点流对象 6 6 in = null; //修改强引用,使底层流引用变量失效,即任何人都不能通过in去引用底层流了 7 7 if (input != null) //然后交给input单独来非空判断和执行关闭操作 8 8 input.close(); //调用节点流的close(),和native相关 9 9 return; 10 10 } 11 11 // Else retry in case a new buf was CASed in fill() 12 12 } 13 13 }