閲讀本文時,請先看我的另一篇博客:6、(InputStream的源碼、FilterInputStream源碼、BufferedInputStream的源碼解讀前言)AtomicReferenceFieldUpdater.class和System.arraycopy()函數的用法
Java IO 庫採用了裝飾器模式(Decorator Pattern)和適配器模式(Adapter Pattern)的組合設計模式,其中InputStream是裝飾器模式中頂層的抽象類,FilterInputStream是裝飾器基類,BufferedInputStream是帶有緩衝區的裝飾器類,ObjectInputStream是可以讀取對象的裝飾器類,FileInputStream、ByteArrayInputStream則是被裝飾的類。裝飾器模式(Decorator Pattern)的詳情,請查看我的另一篇blog四、裝飾者模式
一、InputStream 源碼
InputStream 是所有表示字節輸入流類的父類。windows操作系統的JDK8版本中,所有的InputStream的子類如下(此處只展示部分):

常用的InputStream子類有以下5個:

①、FileInputStream:處理文件的輸入流。
②、ByteArrayInputStream :處理內存中byte[]數組的流式轉換。
③、BufferedInputStream :帶緩衝區的字節數組輸入流,一般配合FileInputStream一起使用(緩衝區可以減少IO次數)。
④、ObjectInputStream:從流中讀入一個自定義的對象。需要與ObjectOutputStream與配合使用,且按同樣的順序(寫入的對象的順序決定了讀取對象的順序)。
⑤、StringBufferInputStream:處理內存中String對象的流式轉換。
以上5個子類的使用方式請參照:我的另一篇博客1、Java的IO概覽(一)
InputStream.class的源碼如下:
package java.io;
public abstract class InputStream implements Closeable {
// skip()函數中可以使用的最大緩衝區大小
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
//留給子類實現,子類必須遵守以下規則:
//1、從輸入的Stream中讀取輸入數據的下一個字節(ASCII碼值),在讀取到數據或者拋出異常前,這個函數是阻塞的。
//2、返回一個0 ~ 255 的 ASCII碼值 。如果因為已經讀到了Stream末尾而沒有可用的字節,則返回值 -1。
public abstract int read() throws IOException;
//將從輸入的Stream中讀取的ASCII碼值放入到byte[]數組中,0表示從byte[]數組的第0個索引開始,b.length表示一次性向byte[]數組中放入的ASCII碼值的數量
//在讀取到數據或者拋出異常前,這個函數是阻塞的。
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
//將從輸入的Stream中讀取的字節(ASCII碼值)放入到byte[]數組中,off表示從byte[]數組的第off個索引開始,len表示一次性向byte[]數組中放入的字節(ASCII碼值)的數量
//在讀取到數據或者拋出異常前,這個函數是阻塞的。
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {//byte[]數組不能為空
throw new NullPointerException();
//範圍檢測,off和len必須是非負數,b.length - off是byte[]數組還可以放的字節(ASCII碼值)的數量
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();//範圍檢查失敗,拋出一個IndexOutOfBoundsException異常
} else if (len == 0) {//len=0,則直接返回0,該Stream可能有可讀的字節(ASCII碼值),也可能沒有可讀的字節(ASCII碼值),但是本次不讀任何數據。
return 0;
}
int c = read();//最終還是調用子類實現的read()函數
if (c == -1) {//read()函數規定了,返回值-1表示已經讀到了Stream末尾而沒有可用的字節(ASCII碼值)
return -1;//如果一開始就讀到了Stream末尾而沒有可用的字節(ASCII碼值),則直接返回-1
}
b[off] = (byte)c;//如果一開始從Stream中可以讀到字節(ASCII碼值),則將讀到的第1個字節(ASCII碼值)值放入byte[]數組的第off個索引位置
//如果從Stream中讀到了第1個字節(ASCII碼值),則接着從Stream中讀後面的字節(ASCII碼值)
int i = 1;
try {
for (; i < len ; i++) {
c = read();//最終還是調用子類實現的read()函數
if (c == -1) {
break;//讀不到,結束循環
}
b[off + i] = (byte)c;//每次從Stream中讀到的字節(ASCII碼值)都放到byte[]數組的第off個索引位置之後
}
} catch (IOException ee) {
}
return i;//返回從Stream中讀到的字節數量
}
//將從輸入的Stream中跳過n個字節
public long skip(long n) throws IOException {
//還沒(或者還需要)跳過的字節(ASCII碼值)的總數量
long remaining = n;
int nr;
//校驗,如果n<=0,則返回0
if (n <= 0) {
return 0;
}
//每次跳過的字節(ASCII碼值)最多為2048個
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
//用於每次跳過指定數量字節(每次最多為2048個)的數組
byte[] skipBuffer = new byte[size];
while (remaining > 0) {//還需要跳過的字節(ASCII碼值)數量<=0時,跳出循環
//調用read(byte b[], int off, int len)函數,該函數在上面已經分析過,該函數的返回值有3種,含義如下:
//①、返回值=-1,表示該Stream沒有可讀的字節
//②、返回值=0,表示該Stream可能有可讀的字節(ASCII碼值),也可能沒有可讀的字節(ASCII碼值),但是本次傳入的Math.min(size, remaining)為0,不從Stream中讀任何數據,此處的Math.min(size, remaining)不可能為0
//③、返回值>0,表示從該Stream中讀到的字節(ASCII碼值)的數量
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;//該Stream中沒有可讀的字節時,nr=-1,跳出循環
}
remaining -= nr;//表示還需要跳過的字節(ASCII碼值)數量
}
return n - remaining;//返回已經跳過的字節(ASCII碼值)的總數量
}
//返回這個Stream中還可以讀取的字節的總數量,JDK不建議將這個函數的返回值作為緩衝區的長度來從Stream中讀取數據(子類一般會覆蓋這個函數)
public int available() throws IOException {
return 0;
}
//留給子類實現,子類必須遵守以下規則:
//關閉Stream,並釋放與該流相關的系統資源
public void close() throws IOException {}
//標記此Stream中的當前位置。隨後調用reset()函數會將此流重新定位到上次標記的位置,從而使得後續的讀取操作能夠再次讀取相同的字節。
//帶有回退功能的InputStream的子類會重寫這個函數,但是FileInputStream不會重寫這個函數,也就意味着,FileInputStream不支持回退功能
public synchronized void mark(int readlimit) {}
//reset()函數會將此流重新定位到上次標記的位置,從而使得後續的讀取操作能夠再次讀取相同的字節。
//帶有回退功能的InputStream的子類會重寫這個函數,但是FileInputStream不會重寫這個函數,也就意味着,FileInputStream不支持回退功能
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
//如果InputStream的子類支持mark()函數和 reset()函數,則返回true,否則,返回false(InputStream的子類不支持mark()函數和 reset()函數)。
public boolean markSupported() {
return false;
}
}
1.1、InputStream的skip()函數
public long skip(long n) throws IOException {
//還沒(或者還需要)跳過的字節(ASCII碼值)的總數量
long remaining = n;
int nr;
//校驗,如果n<=0,則返回0
if (n <= 0) {
return 0;
}
//每次跳過的字節(ASCII碼值)最多為2048個
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
//用於每次跳過指定數量字節(每次最多為2048個)的數組
byte[] skipBuffer = new byte[size];
while (remaining > 0) {//還需要跳過的字節(ASCII碼值)數量<=0時,跳出循環
//調用read(byte b[], int off, int len)函數,該函數在上面已經分析過,該函數的返回值有3種,含義如下:
//①、返回值=-1,表示該Stream沒有可讀的字節
//②、返回值=0,表示該Stream可能有可讀的字節(ASCII碼值),也可能沒有可讀的字節(ASCII碼值),但是本次傳入的Math.min(size, remaining)為0,不從Stream中讀任何數據,此處的Math.min(size, remaining)不可能為0
//③、返回值>0,表示從該Stream中讀到的字節(ASCII碼值)的數量
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;//該Stream中沒有可讀的字節時,nr=-1,跳出循環
}
remaining -= nr;//表示還需要跳過的字節(ASCII碼值)數量
}
return n - remaining;//返回已經跳過的字節(ASCII碼值)的總數量
}
如果要從一個20000個字節的Stream中跳過6000個字節,只需要調用skip(6000)即可,該函數的執行過程分為以下4步:
①、while循環之前進行初始化byte[]數組和零時變量的操作

②、第1次while循環之後,已經從前Stream中讀取了2048個字節

③、第2次while循環之後,已經從前Stream中讀取了4096個字節

④、第3次while循環之後,已經從前Stream中讀取了6000個字節,讀取完畢,byte[]數組的前1094個位置是本次從Stream流中讀取的第4097第6000個字節,byte[]數組的後954個位置仍然是上一次從Stream流中讀取的第3143第4096個字節

二、FilterInputStream 源碼——裝飾器基類
FilterInputStream 的UML關係圖,如下所示:

FilterInputStream.class的源碼,如下所示:
package java.io;
public
class FilterInputStream extends InputStream {
//用來組合了一個 被裝飾者的變量,被修飾為volatile 有以下3個原因:
// 1. 確保多線程環境下修改的可見性
// 2. 有些裝飾器允許運行時替換底層流
// 3. 防止指令重排序導致的初始化問題
protected volatile InputStream in;
//創建時傳入一個 被裝飾者
protected FilterInputStream(InputStream in) {
this.in = in;
}
//調用被裝飾者的read()函數
public int read() throws IOException {
return in.read();
}
//調用被裝飾者的read(byte b[]) 函數
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
//調用被裝飾者的read(byte b[], int off, int len)函數
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
//調用被裝飾者的skip()函數
public long skip(long n) throws IOException {
return in.skip(n);
}
//調用被裝飾者的available()函數
public int available() throws IOException {
return in.available();
}
//調用被裝飾者的close()函數
public void close() throws IOException {
in.close();
}
//調用被裝飾者的mark()函數
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
//調用被裝飾者的reset()函數
public synchronized void reset() throws IOException {
in.reset();
}
//調用被裝飾者的markSupported()函數
public boolean markSupported() {
return in.markSupported();
}
}
三、BufferedInputStream 源碼——帶有緩衝區的裝飾器類
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提供的CAS(Compare-And-Swap)操作來原子地更新某個類中指定變量的值
private static final
AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf");
//緩衝區(byte[]數組)中有效字的節數數量
protected int count;
//準備從緩衝區中(byte[]數組)讀取的字節索引位置,取值範圍為0<=pos<=count
protected int pos;
//在緩衝區(byte[]數組)中標記的某個索引位置,-1<=markpos<=pos
//該變量只會在 fill()函數和mark()函數中賦值
protected int markpos = -1;
// 標記後最多可讀取字節數量,該變量只會在 mark()函數中賦值
//每當pos-markpos>marklimit時,就會設置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;//pos=0,可以從緩衝區(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;//設置pos=pos-markpos
markpos = 0;//設置markpos=0
} 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[]數組)擴容
//如果pos<2147483639/2,則新緩衝區(byte[]數組)的長度nsz=pos*2,否則新緩衝區(byte[]數組)的長度nsz=2147483639
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;//當新緩衝區(byte[]數組)的長度nsz>marklimit,新緩衝區(byte[]數組)的長度nsz=marklimit
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)) {//通過CAS(Compare-And-Swap)操作來原子地更新buf變量
// 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;//新緩衝區(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 {
//pos=count有2種情況(pos不可能>count):
//場景一:pos=count=0,緩衝區(byte[]數組)還沒有填充任何數據。
//場景二:pos=count≠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,off+len)索引位置。
//該函數只被read()函數調用
private int read1(byte[] b, int off, int len) throws IOException {
//只在fill()函數中修改count變量的值,count變量的值只有以下2種可能
//①、count==pos;②、count=從被裝飾的輸入Stream中讀取的字節數量+pos
//因此avail表示緩衝區(byte[]數組)中[pos,pos+count)索引位置的字節數量
int avail = count - pos;
if (avail <= 0) {//緩衝區(byte[]數組)中[pos,pos+count)索引位置的字節數量<=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,off+len)索引位置。
}
fill();//調用fill()函數
avail = count - pos;//重新計算avail
if (avail <= 0) return -1;//如果avail仍然=0,返回-1
}
int cnt = (avail < len) ? avail : len;//此時avail>0,取avail和len中較小的值作為本次從緩衝區(byte[]數組)中讀取的字節數量
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);//從緩衝區(byte[]數組)的pos索引開始,讀取avail或len(2者取其小)個字節到指定的byte[]數組b的[off,off+cnt)索引位置(cnt就是avail或len中2者取其小的值)
pos += cnt;//pos向前移動avail或len(2者取其小)個索引位置
return cnt;//返回avail或len(2者取其小)
}
//線程同步的函數:從緩衝區(byte[]數組)中讀取len個字節到指定的byte[]數組b中,這len個字節被放到byte[]數組b的[off,off+len)索引位置。
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,off+len)索引位置。
int nread = read1(b, off + n, len - n);//nread用來統計每次從read1()函數中讀取一定的字節數量,並放到byte[]數組b的[off,off+len)索引位置。
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種可能
//①、count==pos;②、count=從被裝飾的輸入Stream中讀取的字節數量+pos
//因此avail表示緩衝區(byte[]數組)中[pos,pos+count)索引位置的字節數量
long avail = count - pos;
if (avail <= 0) {//緩衝區(byte[]數組)中[pos,pos+count)索引位置的字節數量<=0(其實不可能<0,只可能=0)
// If no mark position set then don't keep in buffer
if (markpos <0)//同時markpos<0
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;//此時avail>0,取avail和n中較小的值作為本次從緩衝區(byte[]數組)中跳過的字節數量
pos += skipped;//pos向前移動avail或n(2者取其小)個索引位置(與read1()函數異曲同工),表示本次從緩衝區(byte[]數組)中跳過了skipped個字節
return skipped;//返回本次從緩衝區(byte[]數組)中跳過的skipped個字節
}
//線程同步的函數:計算緩衝區(byte[]數組)的最大長度(或者叫容量)
public synchronized int available() throws IOException {
//只在fill()函數中修改count變量的值,count變量的值只有以下2種可能
//①、count==pos;②、count=從被裝飾的輸入Stream中讀取的字節數量+pos
//因此count - pos表示緩衝區(byte[]數組)中[pos,pos+count)索引位置的字節數量
int n = count - pos;
int avail = getInIfOpen().available();//調用被裝飾的輸入Stream的available()函數,返回被裝飾的輸入Stream中仍然可以讀取的字節數量
return n > (Integer.MAX_VALUE - avail)//(緩衝區(byte[]數組)中[pos,pos+count)索引位置的字節數量 + 被裝飾的輸入Stream中仍然可以讀取的字節數量) > 2147483647時,返回2147483647,表示緩衝區(byte[]數組)的最大容量為2147483647
? Integer.MAX_VALUE
: n + avail;//返回(緩衝區(byte[]數組)中[pos,pos+count)索引位置的字節數量 + 被裝飾的輸入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()
}
}
}
3.1、BufferedInputStream的read()函數和fill()函數
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提供的CAS(Compare-And-Swap)操作來原子地更新某個類中指定變量的值
private static final
AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf");
//緩衝區(byte[]數組)中有效字的節數數量
protected int count;
//準備從緩衝區中(byte[]數組)讀取的字節索引位置,取值範圍為0<=pos<=count
protected int pos;
//在緩衝區(byte[]數組)中標記的某個索引位置,-1<=markpos<=pos
//該變量只會在 fill()函數和mark()函數中賦值
protected int markpos = -1;
// 標記後最多可讀取字節數量,該變量只會在 mark()函數中賦值
//每當pos-markpos>marklimit時,就會設置markpos=-1 來刪除標記
protected int marklimit;
//如果緩衝區(byte[]數組)不為空,則返回該緩衝區(byte[]數組),否則拋出異常
private byte[] getBufIfOpen() throws IOException {
byte[] buffer = buf;
if (buffer == null)
throw new IOException("Stream closed");
return buffer;
}
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();//獲取緩衝區(byte[]數組)
if (markpos < 0)//如果還沒有調用過mark()函數,那麼markpos=-1
pos = 0;//pos=0,可以從緩衝區(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;//設置pos=pos-markpos
markpos = 0;//設置markpos=0
} 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[]數組)擴容
//如果pos<2147483639/2,則新緩衝區(byte[]數組)的長度nsz=pos*2,否則新緩衝區(byte[]數組)的長度nsz=2147483639
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;//當新緩衝區(byte[]數組)的長度nsz>marklimit,新緩衝區(byte[]數組)的長度nsz=marklimit
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)) {//通過CAS(Compare-And-Swap)操作來原子地更新buf變量
// 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;//新緩衝區(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 {
//pos=count有2種情況(pos不可能>count):
//場景一:pos=count=0,緩衝區(byte[]數組)還沒有填充任何數據。
//場景二:pos=count≠0,緩衝區(byte[]數組)中的數據已經通過pos讀取完了。
if (pos >= count) {
fill();//符合場景一或場景二都會調用fill()函數
if (pos >= count)
return -1;//如果調用了fill()函數後,仍然符合場景一或場景二,表示被裝飾的輸入Stream已經讀完了,返回-1
}
//執行到這裏時,表明pos < count,返回緩衝區(byte[]數組)pos索引位置的字節;
return getBufIfOpen()[pos++] & 0xff;
}
...省略部分代碼...
}
如果使用者用的是默認的構造函數創建了BufferedInputStream的對象,如下所示(偽代碼):
InputStream is = new FileInputStream("D:\\nio-data.txt");
BufferedInputStream bis = new BufferedInputStream(is);
那麼,BufferedInputStream對象中的緩衝區(byte[]數組)的長度為8192(緩存8KB字節),如果此時執行BufferedInputStream.class::read()函數,
bis.read();
過程如下(假設被裝飾的輸入Stream(FileInputStream)中有10000個字節):
①、pos=count=0,緩衝區(byte[]數組)中還沒有填充任何數據,執行fill()函數,然後將被裝飾的輸入Stream(FileInputStream)中的字節讀取到緩衝區(byte[]數組)的[0,8192)索引位置(左閉右開,不包括byte[]數組的第8192個索引位置),並返回讀取的字節數量。如下所示:

此時,被裝飾的輸入Stream(FileInputStream)中的字節和緩衝區(byte[]數組)中的字節,如下所示:

②、更新int count變量,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為8192,count=8192+pos=8192;
③、執行getBufIfOpen()[pos++] & 0xff,從緩衝區(byte[]數組)中獲取第pos(此時pos=0)個索引位置的字節,返回給BufferedInputStream.class::read()函數的調用方,並更新pos的值為pos+=1;
之後,每次調用BufferedInputStream.class::read()函數時,都不會再執行fill()函數了,直到pos=8192時,執行BufferedInputStream.class::read()函數才會再次執行fill()函數,新的填充緩衝區(byte[]數組)的過程如下:
①、更新pos=0,count=0,緩衝區(byte[]數組)中是上一次執行fill()函數填充的從被裝飾的輸入Stream(FileInputStream)讀取的第18192個字節,本次,需要將被裝飾的輸入Stream(FileInputStream)中的第819310000個字節讀取到緩衝區(byte[]數組)的[0,1808)索引位置(左閉右開,不包括byte[]數組的第1808個索引位置),並返回讀取的字節數量。如下所示:

此時,被裝飾的輸入Stream(FileInputStream)中的字節被全部讀取完畢,被裝飾的輸入Stream(FileInputStream)為空。
②、更新int count變量,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為1808,count=1808+pos=1808;
③、執行getBufIfOpen()[pos++] & 0xff,從緩衝區(byte[]數組)中獲取第pos(此時pos=0)個索引位置的字節,返回給BufferedInputStream.class::read()函數的調用方,並更新pos的值為pos+=1;
之後,每次調用BufferedInputStream.class::read()函數時,都不會再執行fill()函數了,直到pos=1808時,執行BufferedInputStream.class::read()函數才會再次執行fill()函數,過程如下:
①、更新pos=0,count=0,緩衝區(byte[]數組)中的數據如下:

此時,由於被裝飾的輸入Stream(FileInputStream)中的字節被全部讀取完畢,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為0,無法更新count,結束fill()函數的調用
②、執行return -1,返回給BufferedInputStream.class::read()函數的調用方;
3.1.1、如果在多次執行BufferedInputStream.class::read()函數之前執行過mark()函數
標題3.1分析了很多次只調用read()函數之後,最後緩衝區(byte[]數組)中的字節內容,並沒有分析很多次調用read()函數之前,很多次調用read()函數之中,很多次調用read()函數之後分別調用了mark()函數和reset()函數的場景。
如果在很多次調用read()函數之前調用了mark(8192)函數,如下(偽代碼):
InputStream is = new FileInputStream("D:\\nio-data.txt");
BufferedInputStream bis = new BufferedInputStream(is);
bis.mark(8192);//設置marklimit=8192,markpos=pos=0
int bytesRead;
while ((bytesRead = bis.read()) != -1) {
//處理讀取到的字節bytesRead
}
那麼,BufferedInputStream對象中的緩衝區(byte[]數組)的長度為8192(緩存8KB字節),如果此時,如上述代碼一樣在,while循環中執行BufferedInputStream.class::read()函數,過程如下(假設被裝飾的輸入Stream(FileInputStream)中有10000個字節):
①、pos=count=0,緩衝區(byte[]數組)中還沒有填充任何數據,執行fill()函數,然後將被裝飾的輸入Stream(FileInputStream)中的字節讀取到緩衝區(byte[]數組)的[0,8192)索引位置(左閉右開,不包括byte[]數組的第8192個索引位置),並返回讀取的字節數量。如下所示:

此時,被裝飾的輸入Stream(FileInputStream)中的字節和緩衝區(byte[]數組)中的字節,如下所示:

②、更新int count變量,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為8192,count=8192+pos=8192;
③、執行getBufIfOpen()[pos++] & 0xff,從緩衝區(byte[]數組)中獲取第pos(此時pos=0)個索引位置的字節,返回給BufferedInputStream.class::read()函數的調用方,並更新pos的值為pos+=1;
之後,每次調用BufferedInputStream.class::read()函數時,都不會再執行fill()函數了,直到pos=8192時,執行BufferedInputStream.class::read()函數才會再次執行fill()函數,新的填充緩衝區(byte[]數組)的過程如下:
①、此時,因為buffer.length >= marklimit,所以,更新markpos=-1,pos=0;
後續的步驟與標題3.1相同。最終緩衝區(byte[]數組)中的數據如下:

最終,pos=0,count=0,markpos=-1
3.1.2、如果在多次執行BufferedInputStream.class::read()函數之中執行過mark()函數
如果在很多次調用read()函數之中調用了mark(8192)函數,如下(偽代碼):
InputStream is = new FileInputStream("D:\\nio-data.txt");
BufferedInputStream bis = new BufferedInputStream(is);
int bytesRead;
int i = 0;
while ((bytesRead = bis.read()) != -1) {
if(++i==4096){
bis.mark(8192);//設置marklimit=8192,markpos=pos=4096
}
//處理讀取到的字節bytesRead
}
那麼,BufferedInputStream對象中的緩衝區(byte[]數組)的長度為8192(緩存8KB字節),上述代碼的執行過程如下(假設被裝飾的輸入Stream(FileInputStream)中有20000個字節):
①、pos=count=0,緩衝區(byte[]數組)中還沒有填充任何數據,執行fill()函數,然後將被裝飾的輸入Stream(FileInputStream)中的字節讀取到緩衝區(byte[]數組)的[0,8192)索引位置(左閉右開,不包括byte[]數組的第8192個索引位置),並返回讀取的字節數量。如下所示:

此時,被裝飾的輸入Stream(FileInputStream)中的字節和緩衝區(byte[]數組)中的字節,如下所示:

②、更新int count變量,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為8192,count=8192+pos=8192;
③、執行getBufIfOpen()[pos++] & 0xff,從緩衝區(byte[]數組)中獲取第pos(此時pos=0)個索引位置的字節,返回給BufferedInputStream.class::read()函數的調用方,並更新pos的值為pos+=1;
之後,每次調用BufferedInputStream.class::read()函數時,都不會再執行fill()函數了,並且當pos=4096時,執行了bis.mark(8192),設置marklimit=8192,markpos=pos=4096。

直到pos=8192時,執行BufferedInputStream.class::read()函數才會再次執行fill()函數,本次填充緩衝區(byte[]數組)的過程如下:
①、執行fill()函數的如下代碼片段(標題3.1.4也會複用之後的邏輯)
...省略部分代碼...
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;//設置pos=pos-markpos
markpos = 0;//設置markpos=0
}
...省略部分代碼...
先把緩衝區(byte[]數組)中[4096,8192) 索引區間的元素複製到緩衝區(byte[]數組)[0,4096)索引區間,如下所示:

再更新pos=4096,markpos = 0;
②、然後從被裝飾的輸入Stream(FileInputStream)讀取第8193~12286個字節到緩衝區(byte[]數組)的[4096,8192)索引位置(左閉右開,不包括byte[]數組的第8192個索引位置),並返回讀取的字節數量。如下所示:

然後更新count,此時count = n + pos=4096+4096=8192,pos=4096,markpos = 0
③、執行getBufIfOpen()[pos++] & 0xff,從緩衝區(byte[]數組)中獲取第pos(此時pos=4096)個索引位置的字節,返回給BufferedInputStream.class::read()函數的調用方,並更新pos的值為pos+=1;
之後,每次調用BufferedInputStream.class::read()函數時,都不會再執行fill()函數了,直到pos=8192時(此時,count = 8192,markpos=0),執行BufferedInputStream.class::read()函數才會再次執行fill()函數,後續過程分為以下2種情景:
- 情景一,如上偽代碼bis.mark(8192),設置marklimit=8192<=緩衝區(byte[]數組)的長度
①、執行fill()函數的如下代碼片段:
...省略部分代碼...
} else if (buffer.length >= marklimit) {//場景二:pos>=緩衝區(byte[]數組)的長度並且緩衝區(byte[]數組)的長度>= marklimit
markpos = -1; //設置markpos = -1
pos = 0; //設置pos = 0
}
...省略部分代碼...
先更新pos = 0,markpos = -1;然後從被裝飾的輸入Stream(FileInputStream)讀取第12287~20000個字節到緩衝區(byte[]數組)的[0,7914)索引位置(左閉右開,不包括byte[]數組的第7914個索引位置),並返回讀取的字節數量。如下所示:

此時,被裝飾的輸入Stream(FileInputStream)中的字節被全部讀取完畢,被裝飾的輸入Stream(FileInputStream)為空。
②、更新int count變量,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為7914,count=7914+0=7914;
③、執行getBufIfOpen()[pos++] & 0xff,從緩衝區(byte[]數組)中獲取第pos(此時pos=0)個索引位置的字節,返回給BufferedInputStream.class::read()函數的調用方,並更新pos的值為pos+=1;
之後,每次調用BufferedInputStream.class::read()函數時,都不會再執行fill()函數了,直到pos=7914時,執行BufferedInputStream.class::read()函數才會再次執行fill()函數,過程如下:
①、因為markpos = -1,所以更新pos=0,count=0,緩衝區(byte[]數組)中的數據如下:

此時,由於被裝飾的輸入Stream(FileInputStream)中的字節被全部讀取完畢,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為0,無法更新count,結束fill()函數的調用
②、執行return -1,返回給BufferedInputStream.class::read()函數的調用方;
- 情景二,改變上面的偽代碼bis.mark(8192),而是設置marklimit>緩衝區(byte[]數組)的長度(只要是大於8192的任何值都可以),比如bis.mark(16384)
①、執行fill()函數的如下代碼片段,
...省略部分代碼...
} else {//場景四:pos>=緩衝區(byte[]數組)的長度並且不滿足場景一、二、三時,將緩衝區(byte[]數組)擴容
//如果pos<2147483639/2,則新緩衝區(byte[]數組)的長度nsz=pos*2,否則新緩衝區(byte[]數組)的長度nsz=2147483639
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;//當新緩衝區(byte[]數組)的長度nsz>marklimit,新緩衝區(byte[]數組)的長度nsz=marklimit
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)) {//通過CAS(Compare-And-Swap)操作來原子地更新buf變量
// 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;//新緩衝區(byte[]數組)創建完畢
}
...省略部分代碼...
先擴大緩衝區(byte[]數組)的長度到16384(擴大前緩衝區長度為8192),然後將舊緩衝區(byte[]數組)中的內容移動到新緩衝區(byte[]數組)對應的索引位置上,如下所示:

然後通過CAS(Compare-And-Swap)操作來原子地更新buf變量的引用。
②、然後從被裝飾的輸入Stream(FileInputStream)讀取第12287~20000個字節到新緩衝區(byte[]數組)的[8192,16106)索引位置(左閉右開,不包括新byte[]數組的第16106個索引位置),並返回讀取的字節數量。如下所示:

此時,被裝飾的輸入Stream(FileInputStream)中的字節被全部讀取完畢,被裝飾的輸入Stream(FileInputStream)為空。
③、更新int count變量,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為7914,count=7914+pos=16106;
④、執行getBufIfOpen()[pos++] & 0xff,從緩衝區(byte[]數組)中獲取第pos(此時pos=8192)個索引位置的字節,返回給BufferedInputStream.class::read()函數的調用方,並更新pos的值為pos+=1;
之後,每次調用BufferedInputStream.class::read()函數時,都不會再執行fill()函數了,直到pos=16106時,執行BufferedInputStream.class::read()函數才會再次執行fill()函數,過程如下:
①、因為markpos = 0,不會設置pos=0,也不會再執行場景一、場景二、場景三、場景四(標題三源碼中的註釋)、新緩衝區(byte[]數組)中的數據如下:

此時,由於被裝飾的輸入Stream(FileInputStream)中的字節被全部讀取完畢,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為0,無法更新count變量,結束fill()函數的調用
②、執行return -1,返回給BufferedInputStream.class::read()函數的調用方;
3.1.3、如果在多次執行BufferedInputStream.class::read()函數之後執行過mark()函數
如果在很多次調用read()函數之中調用了mark(8192)函數,如下(偽代碼):
InputStream is = new FileInputStream("D:\\nio-data.txt");
BufferedInputStream bis = new BufferedInputStream(is);
int bytesRead;
while ((bytesRead = bis.read()) != -1) {
//處理讀取到的字節bytesRead
}
bis.mark(8192);//設置marklimit=8192,markpos=pos=0
那麼,BufferedInputStream對象中的緩衝區(byte[]數組)的長度為8192(緩存8KB字節),上述代碼的執行過程如下(假設被裝飾的輸入Stream(FileInputStream)中有10000個字節):
參考標題3.1,與標題3.1不同的是,最後執行bis.mark(8192);,設置marklimit=8192,markpos=pos=0。
3.1.4、如果在多次執行BufferedInputStream.class::read()函數之中執行過mark()函數和reset()函數
如果在很多次調用read()函數之中調用了mark(8192)函數,然後又調用了reset()函數,如下(偽代碼):
InputStream is = new FileInputStream("D:\\nio-data.txt");//假設該被裝飾的輸入Stream(FileInputStream)中有20000個字節
BufferedInputStream bis = new BufferedInputStream(is);
int bytesRead;
int i = 0;
while ((bytesRead = bis.read()) != -1) {
if(++i==4096){
bis.mark(8192);//設置marklimit=8192,markpos=pos=4096
}
//處理讀取到的字節bytesRead
if(i==8196){
bis.reset();//當pos=8196時,執行reset()函數,設置pos=markpos=0
}
}
那麼,BufferedInputStream對象中的緩衝區(byte[]數組)的長度為8192(緩存8KB字節),上述代碼的執行過程如下(假設被裝飾的輸入Stream(FileInputStream)中有20000個字節):
①、pos=count=0,緩衝區(byte[]數組)中還沒有填充任何數據,執行fill()函數,然後將被裝飾的輸入Stream(FileInputStream)中的字節讀取到緩衝區(byte[]數組)的[0,8192)索引位置(左閉右開,不包括byte[]數組的第8192個索引位置),並返回讀取的字節數量。如下所示:

此時,被裝飾的輸入Stream(FileInputStream)中的字節和緩衝區(byte[]數組)中的字節,如下所示:

②、更新int count變量,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為8192,count=8192+pos=8192;
③、執行getBufIfOpen()[pos++] & 0xff,從緩衝區(byte[]數組)中獲取第pos(此時pos=0)個索引位置的字節,返回給BufferedInputStream.class::read()函數的調用方,並更新pos的值為pos+=1;
之後,while循環中每次調用BufferedInputStream.class::read()函數時,都不會再執行fill()函數了,並且當pos=4096時,執行了bis.mark(8192),設置marklimit=8192,markpos=pos=4096。

直到pos=8192時,先執行偽代碼中的BufferedInputStream.class::reset()函數,如下所示:
package java.io;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public
class BufferedInputStream extends FilterInputStream {
...省略部分代碼...
//線程同步的函數:pos = markpos(或者叫對齊pos索引位置 到markpos索引位置)
public synchronized void reset() throws IOException {
getBufIfOpen(); //檢測被裝飾的輸入Stream是否關閉
if (markpos < 0)
throw new IOException("Resetting to invalid mark");
pos = markpos;
}
...省略部分代碼...
}
reset()函數會設置pos=markpos=4096,之後,偽代碼的while循環中每次調用BufferedInputStream.class::read()函數時,會將緩衝區(byte[]數組)中第[4096,8192)索引區間的元素再返回一次,並更新pos的值為pos+=1。直到pos=8192時,執行BufferedInputStream.class::read()函數才會再次執行fill()函數,本次填充緩衝區(byte[]數組)的過程如下:
參考標題3.1.2中的第2個序號①和之後的內容;
3.1.5、BufferedInputStream使用時的注意事項
BufferedInputStream中的緩衝區(byte[]數組)如果太小的話(比如長度為12),在執行read()函數時,會被從被裝飾的輸入Stream(假設總共有26個字節)中讀取的新字節覆蓋掉,即使在讀取過程中執行過mark()函數(比如,執行該函數時,pos=6,那麼markpos=6),也只會把本次(第1次填充緩衝區)[markpos,buf.length)索引之間的(左閉右開,實際是[6,12))字節複製到第2次填充的緩衝區(byte[]數組)的[0,6)(左閉右開)索引之間,等到第3次填充緩衝區時,第1次緩衝區中[6,12)索引之間的數據然後被複制到第2次緩衝區(byte[]數組)的[0,6)(左閉右開)索引之間的的數據,仍然會被第3次填充緩衝區時覆蓋掉。因此,使用BufferedInputStream需要注意以下2點:
①、設置的緩衝區(byte[]數組)大小(默認為8192 ,8KB)儘量大於被裝飾的輸入Stream中的數據總量;
②、不建議在多線程中使用BufferedInputStream;
下面這個例子就恰當的使用BufferedInputStream的read()函數、mark()函數、reset()函數:
-
我的windows操作系統的D盤根目錄下有nio-data.txt文件,該文件中總共有31個字節,如下所示:
![clipboard]()
-
當第一次讀取完這個文件中的內容後,該文件中“&”這個字節之後的內容,需要重新讀取一次,如下代碼所示:
package com.xxx.StreamAndReader;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class BufferedInputStreamTest {
public static void main(String[] args) {
InputStream is = null;
BufferedInputStream bis = null;
try {
is = new FileInputStream("D:\\nio-data.txt");//被裝飾的輸入Stream,總共有31個字節(byte)數據
bis = new BufferedInputStream(is, 64);//緩衝區(byte[]數組)的長度為64
System.out.println("第一次讀取被裝飾的輸入Stream中的所有數據:");
int bytesRead;
while ((bytesRead = bis.read()) != -1) {
if (bytesRead == '&') {
bis.mark(64);//當讀取到“&”這個字節後,使用mark()函數做一個標記
}
System.out.print((char) bytesRead);
}
System.out.println();
System.out.println("重複讀取一次標記位置之後的字節:");
bis.reset();//第一次讀取完被裝飾的輸入Stream中的所有數據後,執行reset()函數
while ((bytesRead = bis.read()) != -1) {//從被標記的位置再讀取一次
System.out.print((char) bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) is.close();
if (bis != null) bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
程序運行結果,如下所示:

3.2、BufferedInputStream的read(byte b[], int off, int len)函數和fill()函數
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提供的CAS(Compare-And-Swap)操作來原子地更新某個類中指定變量的值
private static final
AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf");
//緩衝區(byte[]數組)中有效字的節數數量
protected int count;
//準備從緩衝區中(byte[]數組)讀取的字節索引位置,取值範圍為0<=pos<=count
protected int pos;
//在緩衝區(byte[]數組)中標記的某個索引位置,-1<=markpos<=pos
//該變量只會在 fill()函數和mark()函數中賦值
protected int markpos = -1;
// 標記後最多可讀取字節數量,該變量只會在 mark()函數中賦值
//每當pos-markpos>marklimit時,就會設置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;
}
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();//獲取緩衝區(byte[]數組)
if (markpos < 0)//如果還沒有調用過mark()函數,那麼markpos=-1
pos = 0;//pos=0,可以從緩衝區(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;//設置pos=pos-markpos
markpos = 0;//設置markpos=0
} 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[]數組)擴容
//如果pos<2147483639/2,則新緩衝區(byte[]數組)的長度nsz=pos*2,否則新緩衝區(byte[]數組)的長度nsz=2147483639
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;//當新緩衝區(byte[]數組)的長度nsz>marklimit,新緩衝區(byte[]數組)的長度nsz=marklimit
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)) {//通過CAS(Compare-And-Swap)操作來原子地更新buf變量
// 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;//新緩衝區(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[]數組)或被裝飾的輸入Stream中讀取len個字節到指定的byte[]數組b中,這len個字節被放到byte[]數組b的[off,off+len)索引位置。
//該函數只被read()函數調用
private int read1(byte[] b, int off, int len) throws IOException {
//只在fill()函數中修改count變量的值,count變量的值只有以下2種可能
//①、count==pos;②、count=從被裝飾的輸入Stream中讀取的字節數量+pos
//因此avail表示緩衝區(byte[]數組)中[pos,pos+count)索引位置的字節數量
int avail = count - pos;
if (avail <= 0) {//緩衝區(byte[]數組)中[pos,pos+count)索引位置的字節數量<=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,off+len)索引位置。
}
fill();//調用fill()函數
avail = count - pos;//重新計算avail
if (avail <= 0) return -1;//如果avail仍然=0,返回-1
}
int cnt = (avail < len) ? avail : len;//此時avail>0,取avail和len中較小的值作為本次從緩衝區(byte[]數組)中讀取的字節數量
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);//從緩衝區(byte[]數組)的pos索引開始,讀取avail或len(2者取其小)個字節到指定的byte[]數組b的[off,off+cnt)索引位置(cnt就是avail或len中2者取其小的值)
pos += cnt;//pos向前移動avail或len(2者取其小)個索引位置
return cnt;//返回avail或len(2者取其小)
}
//線程同步的函數:從緩衝區(byte[]數組)中讀取len個字節到指定的byte[]數組b中,這len個字節被放到byte[]數組b的[off,off+len)索引位置。
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,off+len)索引位置。
int nread = read1(b, off + n, len - n);//nread用來統計每次從read1()函數中讀取一定的字節數量,並放到byte[]數組b的[off,off+len)索引位置。
if (nread <= 0)//當read1()函數返回0或者-1時,表示緩衝區(byte[]數組)中和被裝飾的輸入Stream中已經沒有可以讀取的字節了
return (n == 0) ? nread : n;//返回累計從緩衝區(byte[]數組)或被裝飾的輸入Stream中讀取的字節數量或者-1
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;
}
}
...省略部分代碼...
}
如果使用者用的是默認的構造函數創建了BufferedInputStream的對象,如下所示(偽代碼):
InputStream is = new FileInputStream("D:\\nio-data.txt");
BufferedInputStream bis = new BufferedInputStream(is);
那麼,BufferedInputStream對象中的緩衝區(byte[]數組)的長度為8192(緩存8KB字節),接下來使用BufferedInputStream對象讀取字節數據到使用者創建的byte[]數組中的過程,分為以下3種情景:
- 情景一,使用者創建的byte[]數組的長度>=緩衝區(byte[]數組)的長度,比如,此處使用者創建的byte[]數組的長度為8192,如下所示(偽代碼):
byte[] buffer = new byte[8192];
bis.read(buffer,0,buffer.length);
整個執行過程如下(直接從被裝飾的輸入Stream中獲取字節,不會使用緩衝區):

- 情景二,使用者創建的byte[]數組的長度<緩衝區(byte[]數組)的長度,比如,此處使用者創建的byte[]數組的長度為1024,如下所示(偽代碼):
byte[] buffer = new byte[1024];
bis.read(buffer,0,buffer.length);
過程如下(假設被裝飾的輸入Stream(FileInputStream)中有10000個字節):
①、先執行到下圖中的紫色部分,如下所示:

②、pos=count=0,緩衝區(byte[]數組)中還沒有填充任何數據,執行fill()函數,然後將被裝飾的輸入Stream(FileInputStream)中的字節讀取到緩衝區(byte[]數組)的[0,8192)索引位置(左閉右開,不包括byte[]數組的第8192個索引位置),並返回讀取的字節數量。如下所示:

此時,被裝飾的輸入Stream(FileInputStream)中的字節和緩衝區(byte[]數組)中的字節,如下所示:

③、更新int count變量,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為8192,count=8192+pos=8192;
④、從緩衝區(byte[]數組)的pos索引(此時,pos=0)開始,讀取1024個字節到使用者創建的byte[]數組的[0,1024)索引位置(左閉右開,不包括第1024個索引位置),並更新pos=1024,read1()函數返回1024,如下所示:

⑤、再執行下圖中的紫色部分之後的流程,如下所示:

- 情景三,使用者創建的byte[]數組的長度<緩衝區(byte[]數組)的長度,比如,此處使用者創建的byte[]數組的長度為1024,但是使用者是在while循環中使用read(byte b[], int off, int len)函數,直到read(byte b[], int off, int len)函數返回-1,如下所示(偽代碼):
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer,0,buffer.length)) != -1) {
for (int i = 0; i < bytesRead; i++) {
//處理讀取到的字節buffer[i]
}
}
過程如下(假設被裝飾的輸入Stream(FileInputStream)中有10000個字節):
①、第1次while循環,先執行到下圖中的紫色部分,如下所示:

此時,pos=count=0,緩衝區(byte[]數組)中還沒有填充任何數據,執行fill()函數,然後將被裝飾的輸入Stream(FileInputStream)中的字節讀取到緩衝區(byte[]數組)的[0,8192)索引位置(左閉右開,不包括byte[]數組的第8192個索引位置),並返回讀取的字節數量。如下所示:

此時,被裝飾的輸入Stream(FileInputStream)中的字節和緩衝區(byte[]數組)中的字節,如下所示:

接着更新int count變量,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為8192,count=8192+pos=8192;
然後,從緩衝區(byte[]數組)的pos索引(此時,pos=0)開始,讀取1024個字節到使用者創建的byte[]數組的[0,1024)索引位置(左閉右開,不包括第1024個索引位置),並更新pos=1024,read1()函數返回1024,如下所示:

最後,再執行下圖中的紫色部分之後的流程,如下所示:

②、第2次while循環,先執行到下圖中的黃色部分,如下所示:

此時,pos=1024,count=8192,從緩衝區(byte[]數組)中讀取1024個字節之後,此時,被裝飾的輸入Stream(FileInputStream)中的字節、緩衝區(byte[]數組)中的字節、和使用者創建的byte[]數組中的數據,如下所示:

然後更新pos=2048,read1()函數返回1024,最後,再執行下圖中的黃色部分之後的流程,如下所示:

③、第3次while循環,先執行到下圖中的黃色部分,如下所示:

此時,pos=2048,count=8192,從緩衝區(byte[]數組)中讀取1024個字節之後,此時,被裝飾的輸入Stream(FileInputStream)中的字節、緩衝區(byte[]數組)中的字節、和使用者創建的byte[]數組中的數據,如下所示:

然後更新pos=3072,read1()函數返回1024,最後,再執行下圖中的黃色部分之後的流程,如下所示:

④、第4次while循環,基本流程與②、③相同,不同處如下所示(pos=40969和使用者創建的byte[]數組中的數據):

⑤、第5次while循環,基本流程與②、③、④相同,不同處如下所示(pos=5120和使用者創建的byte[]數組中的數據):

⑥、第6次while循環,基本流程與②、③、④、⑤相同,不同處如下所示(pos=6144和使用者創建的byte[]數組中的數據):

⑦、第7次while循環,基本流程與②、③、④、⑤、⑥相同,不同處如下所示(pos=7168和使用者創建的byte[]數組中的數據):

⑧、第8次while循環,基本流程與②、③、④、⑤、⑥、⑦相同,不同處如下所示(pos=8192和使用者創建的byte[]數組中的數據):

⑨、第9次while循環,先執行到下圖中的紫色部分,如下所示:

此時,先在fill()函數中更新pos=0,緩衝區(byte[]數組)中是上一次執行fill()函數填充的從被裝飾的輸入Stream(FileInputStream)讀取的第18192個字節,本次,需要將被裝飾的輸入Stream(FileInputStream)中的第819310000個字節讀取到緩衝區(byte[]數組)的[0,1808)索引位置(左閉右開,不包括byte[]數組的第1808個索引位置),並返回讀取的字節數量。如下所示

此時,被裝飾的輸入Stream(FileInputStream)中的字節被全部讀取完畢,被裝飾的輸入Stream(FileInputStream)為空。
然後,更新int count變量,fill()函數中getInIfOpen().read(buffer, pos, buffer.length - pos)這行代碼的返回值為1808,count=1808+pos=1808;
然後,從緩衝區(byte[]數組)的pos索引(此時,pos=0)開始,讀取1024個字節到使用者創建的byte[]數組的[0,1024)索引位置(左閉右開,不包括第1024個索引位置),並更新pos=1024,read1()函數返回1024,如下所示:

最後,再執行下圖中的紫色部分之後的流程,如下所示:

⑩、第10次while循環,先執行到下圖中的黃色部分,如下所示:

此時,pos=1024,count=1808,從緩衝區(byte[]數組)中讀取784個字節之後,此時,被裝飾的輸入Stream(FileInputStream)中的字節、緩衝區(byte[]數組)中的字節、和使用者創建的byte[]數組中的數據,如下所示:

然後更新pos=1808,read1()函數返回784,最後,再執行下圖中的黃色部分之後的流程,如下所示:

⑪、第11次while循環,流程如下所示(最終bis.read(buffer,0,buffer.length)由於的返回值為-1,所以結束了循環):

