Java NIO之Buffer詳細理解

Buffer簡介

Buffer:是一個指定特定數據類型的容器,主要用於和Channel進行數據交互。在多線程操作下 Buffer 是不安全的。

 

在Java NIO中使用的核心緩衝區如下(覆蓋了通過I/O發送的基本數據類型:byte, char、short, int, long, float, double, long):

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

FloatBuffer

DoubleBuffer

LongBuffer

 

Buffer的基本屬性

capacity(容量) :緩衝區能夠容納的數據元素的最大數量。

limit(界限):緩衝區中第一個不能讀或寫的元素位置

position(位置):下一個讀或寫的元素的索引。位置會隨着get()和put()函數更新

mark(標記):標記一個備忘的位置

以上四個屬性的關係:

mark <= position <= limit <= capacity

 

Buffer使用

接下來以最常用的ByteBuffer爲例進行介紹

1.創建Buffer對象

  public static ByteBuffer allocate(int capacity)  //靜態方法,通過內存分配創建Buffer對象

  public static ByteBuffer wrap(byte[] array)   //靜態方法,將[]byte包裝成ByteBuffer對象

  public static ByteBuffer wrap(byte[] array,int offset, int length) //基本同上,但指定了初始位置和長度

  public static ByteBuffer allocateDirect(int capacity) //通過分配直接緩衝區創建Buffer對象

 

  非直接緩衝區:通過allocate()分配緩衝區,將緩衝區建立在JVM的內存中

  直接緩衝區:通過allocateDirect()分配直接緩衝區,將緩衝區建立在物理內存中,可以提高效率。

內核地址空間和用戶地址空間之間形成了一個物理內存映射文件,減少了內核到用戶空間的copy過程

  public abstract boolean isDirect()  //可以通過該方法判斷是否是直接緩衝區

  

  

2.讀寫常用的方法

在Buffer中有兩種模式,一種是寫模式,一種是讀模式。

  put()相關函數:向ByteBuffer中添加元素(byte,[]byte,ByteBuffer等)

  flip()函數:將Buffer從寫模式切換到讀模式。調用flip()方法會將position設回0,並將limit設置成之前position的值。

  get():從Buffer中讀取元素

  hasRemaining():判斷Buffer中是否還有元素可讀

  clear():清空元素

  compact():壓縮元素(擴展空間)

 

利用Buffer讀寫數據,通常遵循四個步驟:

調用put()把數據寫入buffer;

調用flip()從寫模式切換成讀模式;

調用get()從Buffer中讀取數據;

調用buffer.clear()或者buffer.compact() 清除元素

 

Buffer源碼

   構造器

    //構造函數,根據指定的參數來初始化Buffer特定的屬性

    //此構造函數是包私有的

    Buffer(int mark, int pos, int lim, int cap) {       // package-private

        if (cap < 0)

            throw new IllegalArgumentException("Negative capacity: " + cap);

        this.capacity = cap;

        limit(lim);

        position(pos);

        if (mark >= 0) {

            if (mark > pos)

                throw new IllegalArgumentException("mark > position: ("

                                                   + mark + " > " + pos + ")");

            this.mark = mark;

        }

   }

構造器中使用了兩個方法limit()和position()

     函數的功能:設置Buffer的limit,如果position大於newLimit,則將position設置爲新的limit。

    如果mark被定義且大於新的limit,則就會被拋棄。

    public final Buffer limit(int newLimit) {

        //有效性檢查,即limit必須滿足這樣的關係:0<=limit<=position.

        if ((newLimit > capacity) || (newLimit < 0))

            throw new IllegalArgumentException();

        limit = newLimit;

        //如果position大於newLimit,則將position設置爲新的limit。

        if (position > limit) position = limit;

        //如果mark被定義且大於新的limit,則會被拋棄(即設置爲-1)

        if (mark > limit) mark = -1;

        return this;

    }

    

   函數功能:設置Buffer的position.如果mark被定義且大於new position,則就會被拋棄。   

    public final Buffer position(int newPosition) {

        //有效性檢查,即0<=newPosition<=limit.

        if ((newPosition > limit) || (newPosition < 0))

            throw new IllegalArgumentException();

        position = newPosition;

        //如果mark被定義且大於new position,則就會被拋棄。

        if (mark > position) mark = -1;

        return this;

    }

   

allocate()方法

    由於Buffer類是一個抽象類,是不可以實例化對象的,因此在Buffer中是不存在allocate(int cap)方法的,allocate(int cap)方法在其子類中均有實現。這裏就以IntBuffer爲例,看下Buffer子類IntBuffer的allocate(int cap)方法。

    public static IntBuffer allocate(int capacity) {
    
        if (capacity < 0)

            throw new IllegalArgumentException();

        return new HeapIntBuffer(capacity, capacity);

    }

    

函數功能:分配一個新的用來裝載int類型數據的Buffer對象實例。這個new buffer的position爲0,limit爲capacity,mark爲未定義的(即爲-1)。buffer中的元素全部初始化爲零。

 

在allocate方法中直接是實例化了一個IntBuffer子類的對象。

 

既然這裏涉及要HeapIntBuffer類,我們就看下這個類

    public abstract class IntBuffer extends Buffer  implements Comparable<IntBuffer>

    class HeapIntBuffer  extends IntBuffer 

從繼承關係我們知道:HeapIntBuffer這個類是IntBuffer的子類

 

這個類的構造函數爲

    HeapIntBuffer(int cap, int lim) {            // package-private

        super(-1, 0, lim, cap, new int[cap], 0);

    }

在這個構造函數中直接調用了父類IntBuffer中對應的構造函數。看下IntBuffer類中的這個構造函數。

 

在看構造函數之前,這裏要說下IntBuffer類中的幾個屬性。

    final int[] hb;                  // Non-null only for heap buffers

    final int offset;

    boolean isReadOnly;                 // Valid only for heap buffers

IntBuffer類中主要包括一個int類型的數組,即從這裏我們知道,Buffer類的底層數據結構是藉助於數組來完成的。

 

繼續看IntBuffer類的構造方法

    // Creates a new buffer with the given mark, position, limit, capacity,
    // backing array, and array offset
    IntBuffer(int mark, int pos, int lim, int cap,   // package-private
                 int[] hb, int offset)
    {
        super(mark, pos, lim, cap);//調用父類Buffer中對應有參的構造函數
        this.hb = hb;
        this.offset = offset;
    }

  

以上,就是當我們使用如下代碼得到IntBuffer實例的整個過程。

IntBuffer buffer = IntBuffer.allocate(cap);

 

2、put(int i)方法介紹

下面來看IntBuffer類中的put方法

    public abstract IntBuffer put(int i);

    public abstract int get(int index);

 

在IntBuffer類中put、get方法都是抽象的。

有了上面allocate方法的過程分析,我們知道IntBuffer buffer = IntBuffer.allocate(cap)

中的buffer實際上是父類的引用指向的是子類的對象。當我們使用buffer.put(value)/buffer.get().

實際上是調用子類HeapIntBuffer類中的put/get方法,這就是多態。在Java中相當重要的一個特徵。

 

HeapIntBuffer類中put方法的實現如下:

    public IntBuffer put(int x) {
        hb[ix(nextPutIndex())] = x;
        return this;
    }

put方法實現的思想就是:將值存儲在position位置即可。

 

這裏涉及到兩個新的函數,分別爲:

1、nextPutIndex(),函數功能:簡單來說就是返回下一個要寫入元素的索引位置(即當前position值),並將position進行加一操作。

2、ix(int i):偏移offset個位置

這兩個函數的實現如下:

  //函數功能:首先檢查當前的position是否小於limit,如果小於則存儲,否則拋異常。
    final int nextPutIndex() {                          // package-private
        if (position >= limit)
            throw new BufferOverflowException();
        return position++;
    }

    //函數功能:偏移offset個位置
    protected int ix(int i) { 
        return i + offset;
    }


  

3、get()方法介紹

看完了put方法,接下來來看下get方法

 

函數的功能:取得Buffer中position位置所指向的元素。

  //函數功能:獲取buffer中position所指向的元素。
    public int get() {
        return hb[ix(nextGetIndex())];
    }

    //函數功能:獲取buffer中索引爲i位置的元素
    public int get(int i) {
        return hb[ix(checkIndex(i))];
    }

 


  

在get()方法中也涉及到兩個另外的函數:

1、nextGetIndex(),函數功能:返回下一個讀取的元素的索引位置(即position值)

2、ix(int i)

    final int nextGetIndex() {                          // package-private
        if (position >= limit)
            throw new BufferUnderflowException();
        return position++;
    }

    //將position向右移動nb個位置,這個函數目前還沒有看見在哪裏得到的應用
    final int nextGetIndex(int nb) {                    // package-private
        if (limit - position < nb)
            throw new BufferUnderflowException();
        int p = position;
        position += nb;
        return p;
    }

   

以上就是get方法的內部實現,也相當簡單哈。

 

4、flip()方法介紹

其實,在剛開始看別人博客的時候,是最不能理解這個函數的功能的,疑問在於:爲什麼要在讀Buffer中的內容時,要先調用這個方法呢。

 

其實在自己把Buffer的讀模式和寫模式弄清楚之後,這個函數的功能我也就明白了。


     //函數功能:將Buffer從寫模式轉化爲讀模式。
     //具體爲:將limit設置爲此時position並且將position設置爲零。如果mark被定義了則被拋棄(即設置爲-1)
     //這個方法的應用場景爲:在管道讀或調用put方法之後,調用此方法來爲管道寫或者是get操作做準備。
    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

  

以上就是flip()方法的介紹,是不是也比較簡單哈。

 

5、hasRemaining()介紹

函數功能:判斷Buffer中是否還有可讀取的元素。

    public final boolean hasRemaining() {
        return position < limit;
    }  

此方法的內部實現直接是判斷position是否小於limit.

所以,邏輯也是相當簡單的。

 

6、clear() compact()方法的介紹

clear()函數功能:清空Buffer中所有的元素。

 

其內部實現直接是將Buffer裏面幾個屬性進行了重置。
  

  public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

最後看下compact方法

compact()方法的功能:將已讀元素進行清除,未讀元素拷貝保留並拷貝到Buffer最開始位置。這個也是和clear()方法不同的地方。

具體實現如下:

    public IntBuffer compact() {
        //先進行拷貝,即將剩餘的沒有訪問的元素拷貝到Buffer從零開始的位置
        System.arraycopy(hb, ix(position()), hb, ix(0), remaining());//remaining()函數返回的是剩餘元素個數
        //設置position爲下一個寫入元素的位置
        position(remaining());
        //設置limit爲Buffer容量
        limit(capacity());
        discardMark();//廢棄mark,即將mark設置爲-1
        return this;
    }



    public final int remaining() {
        return limit - position;
    }

  


  

 

以上內容主要參考

《Java NIO》

https://blog.csdn.net/u010412719/article/details/52775637

https://blog.csdn.net/u010853261/article/details/53464397

https://www.cnblogs.com/KKSJS/p/9909587.html

https://www.cnblogs.com/snailclimb/p/Buffer.html

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章