博客 / 詳情

返回

7、InputStream的源碼、FilterInputStream源碼、BufferedInputStream的源碼(windows操作系統,JDK8)

  閲讀本文時,請先看我的另一篇博客: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的子類如下(此處只展示部分):
clipboard

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

①、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[]數組和零時變量的操作
clipboard

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

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

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

二、FilterInputStream 源碼——裝飾器基類

  FilterInputStream 的UML關係圖,如下所示:
clipboard

  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關係圖,如下所示:
clipboard

  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個索引位置),並返回讀取的字節數量。如下所示:
clipboard

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

②、更新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個索引位置),並返回讀取的字節數量。如下所示:
clipboard

此時,被裝飾的輸入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[]數組)中的數據如下:
clipboard

此時,由於被裝飾的輸入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個索引位置),並返回讀取的字節數量。如下所示:
clipboard

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

②、更新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[]數組)中的數據如下:
clipboard

最終,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個索引位置),並返回讀取的字節數量。如下所示:
clipboard

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

②、更新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。
clipboard

  直到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)索引區間,如下所示:
clipboard

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

然後更新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個索引位置),並返回讀取的字節數量。如下所示:
clipboard

此時,被裝飾的輸入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[]數組)中的數據如下:
clipboard

此時,由於被裝飾的輸入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[]數組)對應的索引位置上,如下所示:
clipboard

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

此時,被裝飾的輸入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[]數組)中的數據如下:
clipboard

此時,由於被裝飾的輸入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個索引位置),並返回讀取的字節數量。如下所示:
88291

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

②、更新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。
88290

直到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();
         }
      }
   }
}

程序運行結果,如下所示:
clipboard

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中獲取字節,不會使用緩衝區):
clipboard

  • 情景二,使用者創建的byte[]數組的長度<緩衝區(byte[]數組)的長度,比如,此處使用者創建的byte[]數組的長度為1024,如下所示(偽代碼):
byte[] buffer = new byte[1024];
bis.read(buffer,0,buffer.length);

過程如下(假設被裝飾的輸入Stream(FileInputStream)中有10000個字節):
①、先執行到下圖中的紫色部分,如下所示:
clipboard

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

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

③、更新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,如下所示:
clipboard

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

  • 情景三,使用者創建的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循環,先執行到下圖中的紫色部分,如下所示:
clipboard

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

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

接着更新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,如下所示:
clipboard

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

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

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

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

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

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

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

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

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

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

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

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

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

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

此時,被裝飾的輸入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,如下所示:
clipboard

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

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

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

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

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

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.