緩衝區 Buffer 是 Java NIO 中一個核心概念,它是一個線性結構,容量有限,存放原始類型數據(boolean 除外)的容器。css
java.nio.Buffer 是一個接口,有 7 個重要的子類,對應着 7 種(除 boolean 外)原始數據類型:IntBuffer, CharBuffer, FloatBuffer, DoubleBuffer, ShortBuffer, LongBuffer, ByteBuffer。java
設計模式
從類圖中能夠看到,7 種數據類型對應着 7 種子類,這些名字是 Heap 開頭子類,數據是存放在 JVM 堆中的;而 MappedByteBuffer 則是存放在堆外的直接內存中,能夠映射到文件。數組
上面所列舉的 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
上面分配的緩衝區存儲結構是數組,數組是存放在虛擬機堆內存中的,這一類 Buffer 也能夠稱爲堆緩衝區;事實上,調用 CharBuffer.allocate(capacity) 方法分配的緩衝區就是一個 HeapCharBuffer 的實例。相應地,有 7 種堆緩衝區。不過 ByteBuffer 有些特殊,它除了有常規地 HeapByteBuffer 以外,還有一個 MappedByteBuffer,它基於內存映射技術,內容是文件在內存中的映射;使用 MappedByteBuffer 訪問大文件效率要比 HeapByteBuffer 更高,這裏不做討論,其它文章有單獨的介紹。
一個 Buffer 能夠處於讀模式或者寫模式,兩種模式能夠發生切換。一個剛建立的 Buffer 處於寫模式,此時能夠往裏面寫入數據。
put(char c) 方法能夠往 Buffer 中寫入數據。CharBuffer 使用了建造者設計模式,大部分方法都返回了當前對象,可使用鏈式調用往 Buffer 中寫入數據。
buf.put('A').put('B').put('C').put('D').put('E').put('F').put('G');
每寫入一個 char 數據,position 就移動 1 位。
flip() 可以將 Buffer 從寫入模式切換成讀取模式,它的原理很簡單,就是將 limit 移動到了 position 所指位置,將 position 移到了 0 位置。
buf.flip();
buf 內部結構的變化以下圖所示。
get() 方法可以讀取 Buffer 中的數據,它將 position 所指的元素返回,而後 position 移動 1 個位置,指向下一個應該返回的元素。get() 由於要返回元素,因此它不支持鏈式調用。
System.out.println(buf.get()); // 輸出:A System.out.println(buf.get()); // 輸出:B System.out.println(buf.get()); // 輸出:C
mark() 將記錄當前 positon 的位置,reset() 方法須要配合 mark() 使用。調用 mark() 方法的時候,position 將賦值給 mark 屬性;能夠繼續讀取,position 往前移動;調用 reset() 以後,position 會從新回退到 mark 位置。
buf.mark(); System.out.println(buf.get()); // 輸出:D buf.reset();
mark() 配合 reset() 容許從新讀取部分元素,rewind() 容許從新讀取所有元素。rewind() 方法直接讓 position 指向 0 位置,而後將 mark 清除。
想象這樣一個場景:某個 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() 僅僅是複製了數據,而並無將數組中的其它元素設置爲零值,以下圖所示。
clear() 方法能夠認爲是將 Buffer 重置了,將 Buffer 切換到了寫模式。實際上它只是將 position, limit 的值重置了,position=0, limit = capacity。
Buffer 是一個緩衝數據的容器,其內部結構是一個固定長度的數組,支持 7 種數據的存取。
Buffer 是非線程安全的,多線程併發訪問一個 Buffer 時應該進行同步。
Buffer 有讀取和寫入兩種模式,剛建立時處於寫入模式,模式之間能夠進行切換。其模式切換關係以下圖所示。
須要明確的是,通常狀況下,Buffer 中並無明確限制讀取模式中 Buffer 只能讀取,寫入模式中只能寫入。例如一個剛建立的 buf 也能夠直接調用 get() 方法。不過按照這種模式的限制操做一個 Buffer 不容易使數據出錯。另外,Buffer 中也提供了 asReadOnlyBuffer() 方法來基於原 Buffer 建立一個新的只讀 Buffer。