Java NIO 緩衝區 Buffer

緩衝區 Buffer 是 Java NIO 中一個核心概念,它是一個線性結構,容量有限,存放原始類型數據(boolean 除外)的容器。css

1. Buffer 中能夠存放的數據類型

java.nio.Buffer 是一個接口,有 7 個重要的子類,對應着 7 種(除 boolean 外)原始數據類型:IntBuffer, CharBuffer, FloatBuffer, DoubleBuffer, ShortBuffer, LongBuffer, ByteBuffer。java

Buffer IntBuffer FloatBuffer CharBuffer LongBuffer ByteBuffer DoubleBuffer ShortBuffer HeapIntBuffer HeapFloatBuffer HeapCharBuffer HeapLongBuffer HeapByteBuffer HeapDoubleBuffer HeapShortBuffer MappedByteBuffer DirectByteBuffer 設計模式

從類圖中能夠看到,7 種數據類型對應着 7 種子類,這些名字是 Heap 開頭子類,數據是存放在 JVM 堆中的;而 MappedByteBuffer 則是存放在堆外的直接內存中,能夠映射到文件。數組

2. 建立一個 Buffer

上面所列舉的 7 個類是抽象類,不能直接使用 new 關鍵字來實例化,而應該調用 allocate(int capacity) 方法來實例化一個 Buffer。安全

CharBuffer buf = CharBuffer.allocate(10);

每一個 Buffer 都有其固定的容量,上面這句代碼表示建立一個容量爲 10 的 CharBuffer,最多能夠往 Buffer 中存放 10 個 char 字符。Buffer 中有 3 個重要的屬性 capacity, limit, position:多線程

capacity 是一個不可改變的非負整數,它在建立 Buffer 的時候設置。如上面建立的 buf 的 capacity 就是 10。併發

limit 也是一個非負整數,其值不能超過 capacity。它指向 Buffer 中第 1 個不可以被讀或者寫的元素。app

position 表示 Buffer 中下一個被讀取或者寫入的單元。svg

下圖爲一個 CharBuffer 剛建立時的結構。它的內部存儲結構是一個 char[] 數組,capacity 和 limit 指向 10 的位置,position 指向位置 0 。同理,一個 IntBuffer 內部的存儲結構爲一個 int[] 數組。spa

7 8 5 6 9 2 3 0 1 4 capacity / limit position allocate(10) 可寫
上面分配的緩衝區存儲結構是數組,數組是存放在虛擬機堆內存中的,這一類 Buffer 也能夠稱爲堆緩衝區;事實上,調用 CharBuffer.allocate(capacity) 方法分配的緩衝區就是一個 HeapCharBuffer 的實例。相應地,有 7 種堆緩衝區。不過 ByteBuffer 有些特殊,它除了有常規地 HeapByteBuffer 以外,還有一個 MappedByteBuffer,它基於內存映射技術,內容是文件在內存中的映射;使用 MappedByteBuffer 訪問大文件效率要比 HeapByteBuffer 更高,這裏不做討論,其它文章有單獨的介紹。

3. Buffer 的讀和寫

一個 Buffer 能夠處於讀模式或者寫模式,兩種模式能夠發生切換。一個剛建立的 Buffer 處於寫模式,此時能夠往裏面寫入數據。

3.1 put(char c) 寫入數據

put(char c) 方法能夠往 Buffer 中寫入數據。CharBuffer 使用了建造者設計模式,大部分方法都返回了當前對象,可使用鏈式調用往 Buffer 中寫入數據。

buf.put('A').put('B').put('C').put('D').put('E').put('F').put('G');

每寫入一個 char 數據,position 就移動 1 位。

7 8 5 6 9 2 3 0 1 4 capacity / limit position 可寫 put('A') ... put('G') 'F' 'G' 'C' 'D' 'A' 'B' 'E' 7 8 5 6 9 2 3 0 1 4 capacity / limit position 可寫

3.2 flip() 將寫模式切換到讀模式

flip() 可以將 Buffer 從寫入模式切換成讀取模式,它的原理很簡單,就是將 limit 移動到了 position 所指位置,將 position 移到了 0 位置。

buf.flip();

buf 內部結構的變化以下圖所示。

'F' 'G' 'C' 'D' 'A' 'B' 'E' 7 8 5 6 9 2 3 0 1 4 capacity / limit position 可寫 'F' 'G' 'C' 'D' 'A' 'B' 'E' 7 8 5 6 9 2 3 0 1 4 capacity position 可讀 limit flip()

3.3 get() 讀取數據

get() 方法可以讀取 Buffer 中的數據,它將 position 所指的元素返回,而後 position 移動 1 個位置,指向下一個應該返回的元素。get() 由於要返回元素,因此它不支持鏈式調用。

System.out.println(buf.get()); // 輸出:A
System.out.println(buf.get()); // 輸出:B
System.out.println(buf.get()); // 輸出:C

'F' 'G' 'C' 'D' 'A' 'B' 'E' 7 8 5 6 9 2 3 0 1 4 capacity position 可讀 limit 'F' 'G' 'C' 'D' 'A' 'B' 'E' 7 8 5 6 9 2 3 0 1 4 capacity position 可讀 limit get(),get(), get()

3.4 mark() / reset() / rewind()

mark() 將記錄當前 positon 的位置,reset() 方法須要配合 mark() 使用。調用 mark() 方法的時候,position 將賦值給 mark 屬性;能夠繼續讀取,position 往前移動;調用 reset() 以後,position 會從新回退到 mark 位置。

buf.mark();
System.out.println(buf.get()); // 輸出:D
buf.reset();

'F' 'G' 'C' 'D' 'A' 'B' 'E' 7 8 5 6 9 2 3 0 1 4 capacity position 可讀 limit 'F' 'G' 'C' 'D' 'A' 'B' 'E' 7 8 5 6 9 2 3 0 1 4 capacity mark 可讀 limit mark() get() position 'F' 'G' 'C' 'D' 'A' 'B' 'E' 7 8 5 6 9 2 3 0 1 4 mark/position 可讀 limit capacity reset()

mark() 配合 reset() 容許從新讀取部分元素,rewind() 容許從新讀取所有元素。rewind() 方法直接讓 position 指向 0 位置,而後將 mark 清除。

3.5 compact()

想象這樣一個場景:某個 Buffer 中存儲了一條半消息,剩下半條消息的數據尚未所有傳輸過來;此時須要讓 Buffer 切換到讀取模式,讀取第 1 條消息,讓後讓剩下的半條消息的數據留在 Buffer 中。compact() 能夠很好地處理這樣地場景。

compact() 能夠將一個未讀取完的 Buffer 切換到寫模式。它的具體作法是將 [position, limit) 範圍內的數據複製到 [0, limit-position),而後 position = limit - position, limit = capacity。此時又能夠往 Buffer 中寫數據而不會影響未讀取的數據了。

buf.compact();
System.out.println(Arrays.toString(buf.array())); // 輸出:[D, E, F, G, E, F, G, , , ]

須要注意的是,compact() 僅僅是複製了數據,而並無將數組中的其它元素設置爲零值,以下圖所示。

'F' 'G' 'C' 'D' 'A' 'B' 'E' 7 8 5 6 9 2 3 0 1 4 position 可讀 limit 'F' 'G' 'F' 'G' 'D' 'E' 'E' 7 8 5 6 9 2 3 0 1 4 position 可寫 capacity capacity / limit compact()

3.6 clear()

clear() 方法能夠認爲是將 Buffer 重置了,將 Buffer 切換到了寫模式。實際上它只是將 position, limit 的值重置了,position=0, limit = capacity。

4. 小結

Buffer 是一個緩衝數據的容器,其內部結構是一個固定長度的數組,支持 7 種數據的存取。

Buffer 是非線程安全的,多線程併發訪問一個 Buffer 時應該進行同步。

Buffer 有讀取和寫入兩種模式,剛建立時處於寫入模式,模式之間能夠進行切換。其模式切換關係以下圖所示。

可寫 可讀 allocate() flip() get() / mark() / reset() / rewind() compact() / clear() put()

須要明確的是,通常狀況下,Buffer 中並無明確限制讀取模式中 Buffer 只能讀取,寫入模式中只能寫入。例如一個剛建立的 buf 也能夠直接調用 get() 方法。不過按照這種模式的限制操做一個 Buffer 不容易使數據出錯。另外,Buffer 中也提供了 asReadOnlyBuffer() 方法來基於原 Buffer 建立一個新的只讀 Buffer。

相關文章
相關標籤/搜索