1. 程式人生 > >NIO 之 Buffer 圖解

NIO 之 Buffer 圖解

Buffer 類 結構

對於每個非布林原始資料型別都有一個緩衝區類。儘管緩衝區作用於它們儲存的原始資料型別,但緩衝區十分傾向於處理位元組。

概述

緩衝區 Buffer 內部就是用陣列實現的。 Buffer 包含了下面4個屬性:

  • 容量( Capacity) 緩衝區能夠容納的資料元素的最大數量。這一容量在緩衝區建立時被設定,並且永遠不能被改變。
  • 上界( Limit) 緩衝區的第一個不能被讀或寫的元素。或者說,緩衝區中現存元素的計數。
  • 位置( Position) 下一個要被讀或寫的元素的索引。位置會自動由相應的 get( )和 put( )函式更新。
  • 標記( Mark) 一個備忘位置。呼叫 mark( )來設定 mark = postion。呼叫 reset( )設定 position = mark。標記在設定前是未定義的(undefined)。

這四個屬性之間總是遵循以下關係: 0 <= mark <= position <= limit <= capacity

示例

下面展示了一個新建立的容量為 10 的 ByteBuffer 邏輯檢視

ByteBuffer.allocate(10);

圖1

位置(Position)被設為 0,而且容量( Capacity)和上界( Limit)被設為 10,剛好經過緩衝區能夠容納的最後一個位元組。 標記(mark)最初未定義。 容量(Capacity)是固定的,但另外的三個屬性可以在使用緩衝區時改變。

put() 方法

讓我們看一個例子。 我們將代表“abcde”字串的 ASCII 碼載入一個名為 buffer 的 ByteBuffer 物件中。當在圖1 中所新建的緩衝區上執行以下程式碼後。

buffer.put((byte)'a').put((byte)'b').put((byte)'c').put((byte)'d').put((byte)'e');

緩衝區的結果狀態如圖 2所示:

圖2

flip() 方法

我們已經寫滿了緩衝區,現在我們必須準備將其清空。我們想把這個緩衝區傳遞給一個通 道,以使內容能被全部寫出。但如果通道現在在緩衝區上執行 get(),那麼它將從我們剛剛插入的有用資料之外取出未定義資料。如果我們將位置值重新設為 0,通道就會從正確位置開始獲取,但是它是怎樣知道何時到達我們所插入資料末端的呢?這就是上界屬性被引入的目的。上界屬性指明瞭緩衝區有效內容的末端。我們需要將上界屬性設定為當前位置,然後將位置重置為 0。

flip()函式將一個能夠繼續新增資料元素的填充狀態的緩衝區翻轉成一個準備讀出元素 的釋放狀態。在翻轉之後,圖 2 的緩衝區會變成圖 3 中的樣子。

圖3

rewind() 方法

rewind()函式與 flip()相似,但不影響上界屬性。它只是將位置值設回 0。您可以使 用 rewind()後退,重讀已經被翻轉的緩衝區中的資料。 圖2 的緩衝區呼叫 rewind() 方法會變成圖4 中的樣子。

圖4

如果將緩衝區翻轉兩次會怎樣呢?

compact() 方法

有時,您可能只想從緩衝區中釋放一部分資料,而不是全部,然後重新填充。為了實現這 一點,未讀的資料元素需要下移以使第一個元素索引為 0。儘管重複這樣做會效率低下,但這有時非常必要,而 API 對此為您提供了一個 compact()函式。這一緩衝區工具在複製資料時要比您使用 get()和 put()函式高效得多。所以當您需要時,請使用 compact()。圖 5顯示了一個讀取了兩個元素(position 現在為2),並且現在我們想要對其進行壓縮的緩衝區。

圖5

buffer.compact();

壓縮後的結果如下圖

圖6

duplicate() 方法

duplicate() 方法建立了一個與原始緩衝區一樣的新緩衝區。兩個緩衝區共享資料,擁有同樣的 capacity ,但每個緩衝區都擁有自己的 position,limit 和 mark 屬性。對一個緩衝區內的資料元素所做的改變會反映在另外一個緩衝區上。這一副本緩衝區具有與原始緩衝區同樣的資料檢視。如果原始的緩衝區為只讀,或者為直接緩衝區,新的緩衝區將繼承這些屬性。

    public ByteBuffer duplicate() {
        return new HeapByteBufferR(hb,
                                        this.markValue(),
                                        this.position(),
                                        this.limit(),
                                        this.capacity(),
                                        offset);
    }

重新建立一個 ByteBuffer,並且使用同一個陣列。所有一個byteBuffer 變動,會影響另一個 ByteBuffer。 但 position、limit、mark 都是獨立的。

duplicate() 方法

您 可 以 使 用 asReadOnlyBuffer() 函 數 來 生 成 一 個 只 讀 的 緩 衝 區 視 圖 。 這 與 duplicate()相同,除了這個新的緩衝區不允許使用 put(),並且其 isReadOnly()函式 將 會 返 回 true 。 對 這 一 只 讀 緩 衝 區 的 put() 函 數 的 調 用 嘗 試 會 導 致 拋 出 ReadOnlyBufferException 異常。

public ByteBuffer asReadOnlyBuffer() {
        return new HeapByteBufferR(hb,
                                     this.markValue(),
                                     this.position(),
                                     this.limit(),
                                     this.capacity(),
                                     offset);
}

HeapByteBufferR 分析

class HeapByteBufferR
    extends HeapByteBuffer{
    public ByteBuffer put(byte x) {
        throw new ReadOnlyBufferException();
    }

    public ByteBuffer put(int i, byte x) {
        throw new ReadOnlyBufferException();
    }

    public ByteBuffer putInt(int x) {
        throw new ReadOnlyBufferException();
    }
    ......
}

HeapByteBufferR 繼承 HeapByteBuffer 類,並重寫了所有的可修改 buffer 的方法。把所有能修改 buffer 的方法都直接 throw ReadOnlyBufferException,來保證只讀。

slice() 方法

slice() 分割緩衝區。建立一個從原始緩衝區的當前位置開始的新緩衝區,並且其容量是原始緩衝區的剩餘元素數量( limit-position)。這個新緩衝區與原始緩衝區共享一段資料元素子序列。分割出來的緩衝區也會繼承只讀和直接屬性。

原 ByteBuffer如下圖:

slice() 分割後的 ByteBuffer

    public ByteBuffer slice() {
        return new HeapByteBuffer(hb,
                                        -1,
                                        0,
                                        this.remaining(),
                                        this.remaining(),
                                        this.position() + offset);
    }