Java NIO系列(二) - Buffer
前言
在Java NIO
中,緩沖區用來臨時存儲數據,可以理解為是I/O
操作中數據暫存的中轉站。緩沖區直接為通道(Channel
)服務,數據是從通道讀入緩沖區,從緩沖區寫入到通道中的。
緩沖區本質上是一塊可以寫入數據,然後可以從中讀取數據的內存。這塊內存被包裝成NIO Buffer
對象,並提供了一組方法,用來方便的訪問這塊內存。
正文
Buffer的類型
Java NIO
提供以下幾種Buffer
類型:
- ByteBuffer
- MappedByteBuffer
- ShortBuffer
- LongBuffer
- FloatBuffer
- CharBuffer
- IntBuffer
- DoubleBuffer
這些Buffer
類型代表了Java
中7種基本數據類型。換句話說,就是可以通過byte
、char
、short
、int
、long
、float
或double
類型來操作緩沖區中的數據。
Buffer的基本用法
使用Buffer
讀寫數據一般遵循以下四個步驟:
- 寫入數據到
Buffer
中; - 調用
Buffer
的flip()
方法; - 從
Buffer
中讀取數據; - 調用
clear()
方法或者compact()
方法。
當向Buffer
寫入數據時,Buffer
會記錄下寫了多少數據。一旦要讀取數據,需要通過flip()
方法將Buffer
從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到Buffer
的所有數據。
一旦讀完了所有的數據,就需要清空緩沖區
clear()
或compact()
方法。
-
clear()方法:清空整個緩沖區,包括已讀和未讀的數據。
-
compact()方法:只會清空已讀的數據,未讀的數據都被移到緩沖區的起始處,新寫入的數據將放到緩沖區未讀數據的後面。
下面給出一個ByteBuffer
的簡單使用示例,其他緩沖區API
的使用類似:
1
|
public static void testReadFromBuffer() {
|
Buffer的重要屬性
為了理解Buffer
的工作原理,需要熟悉它的4
個核心屬性:
屬性 | 含義 | 具體描述 |
---|---|---|
capacity | 容量 | 緩沖區可以容納的最大數據量,在緩沖區創建時被設定並且不能改變 |
limit | 上界 | 緩沖區中當前已使用的數據量 |
position | 位置 | 緩沖區下一個要被讀或寫的元素的索引 |
mark | 標記 | 調用mark()來設置mark=position,再調用reset()可以讓position恢復到標記的位置即position=mark |
其中,position
和limit
的含義取決於Buffer
處在讀模式還是寫模式。不管Buffer
處在什麽模式,capacity
的含義總是一樣的。
capacity
作為一個內存塊,Buffer
有一個固定的大小值,也叫capacity
。你最多只能寫入capacity
個的byte
、char
、int
、long
等類型數據。一旦Buffer
滿了,需要將其清空(通過讀數據或者清除數據)才能繼續往裏寫數據。
position
- 寫入數據時:
當你寫數據到Buffer
中時,position
表示下一個可寫入的數據的位置。position
的初始位置為0
,當一個byte
、char
、int
、long
等數據寫到Buffer
後,position
會向前移動到下一個可插入數據的Buffer
單元。position
最大可為capacity – 1
。
- 讀取數據時:
當從Buffer
讀取數據時,position
表示下一個可讀取的數據的位置。當將Buffer
從寫模式切換到讀模式,position
會被重置為0
。當從Buffer
的position
處讀取數據時,position
向前移動到下一個可讀的位置。
limit
- 寫入數據時:
在寫模式下,Buffer
的limit
表示你最多能往Buffer
裏寫多少數據。寫模式下,limit
等於Buffer
的capacity
,也就是內存塊的最大容量。
- 寫入數據時:
當切換Buffer
到讀模式時,limit
會被設置成寫模式下的position
值,limit
表示你最多能讀到多少數據。limit
被設置成已寫數據的數量,這個值在寫模式下就是position
。
Buffer的方法
Buffer的分配
要想獲得一個Buffer
對象首先要進行分配,每一個Buffer
類都有一個allocate()
方法。下面是一個分配10
字節capacity
的ByteBuffer
的例子:
1
|
ByteBuffer buf = ByteBuffer.allocate(10);
|
這是分配一個可存儲1024
個字符的CharBuffer
:
1
|
CharBuffer buf = CharBuffer.allocate(1024);
|
向Buffer中寫入數據
寫數據到Buffer
有兩種方式:
- 從
Channel
將數據寫入Buffer
。
1
|
int bytesRead = channel.read(buffer);
|
- 通過
Buffer
的put()
方法寫到Buffer
中。
1
|
buffer.put(1);
|
put()
在ByteBuffer
中為抽象方法,在ByteBuffer
有很多的重載,由其子類HeapByteBuffer
和DirectByteBuffer
實現。
寫模式切換為讀模式
flip()
方法將Buffer
從寫模式切換到讀模式。調用flip()
方法會將position
設回0
,並將limit
設置成之前position
的值。
1
|
buffer.flip();
|
查看flip()
方法的源碼確認:
1
|
public final Buffer flip() {
|
從Buffer從讀取數據
從Buffer
中讀取數據也有兩種方式:
- 從
Buffer
讀取數據到Channel
中。
1
|
int bytesWritten = channel.write(buf);
|
- 通過
Buffer
的get()
方法從Buffer
中讀取數據。
1
|
byte b = buffer.get();
|
get()
方法和put()
一樣有很多的重載,允許以不同的方式從Buffer
中讀取數據。例如:從指定position
讀取,或者從Buffer
中讀取數據到字節數組。
clear()和compact()方法
一旦讀完Buffer
中的數據,需要讓Buffer
準備好再次被寫入。前面也說了,可以通過clear()
或compact()
方法來完成。
clear()方法
如果調用的是clear()
方法,position
將被設回0
,limit
被設置成capacity
的值。
1
|
buffer.clear();
|
查看clear()
方法的源碼確認:
1
|
public final Buffer clear() {
|
換句話說,Buffer
被清空了。Buffer
中的數據並未清除,只是這些標記告訴我們可以從哪裏開始往Buffer
裏寫數據。
compact()方法
如果調用的是compact()
方法,所有的未讀數據都將被拷貝到Buffer
的起始位置,position
會設置為最後一個未讀元素的後面。limit()
方法和clear()
方法一樣,會被設置為capacity
的大小。
查看compact()
方法的實現,此方法在ByteBuffer
中為抽象方法,查看其子類HeapByteBuffer
的實現:
1
|
public ByteBuffer compact() {
|
現在Buffer
準備好寫數據了,但是不會覆蓋未讀的數據。
mark()與reset()方法
通過調用mark()
方法,可以標記Buffer
中的一個特定position
。之後可以通過調用reset()
方法恢復到這個position
。例如:
mark()方法
1
|
buffer.mark();
|
查看mark()
方法的源碼,mark
變量被設置為position
的值:
1
|
public final Buffer mark() {
|
reset()方法
1
|
buffer.reset();
|
查看mark()
方法的源碼,position
變量被設置為之前的mark
的值:
1
|
public final Buffer reset() {
|
equals()與compareTo()方法
可以使用equals()
和compareTo()
方法比較兩個Buffer
。
equals()方法
當同時滿足下列條件時,表示兩個Buffer
相等:
- 有相同的類型(
byte
、char
、int
和long
類型等)。 Buffer
中剩余的byte
、char
等元素的個數相等。Buffer
中所有剩余的byte
、char
等都相同。
equals()
方法比較的實際是Buffer
中的剩余元素是否相等。它只是比較Buffer
的一部分,不是每一個在它裏面的元素都比較。
compareTo()方法
compareTo()
方法比較兩個Buffer
的剩余元素(byte
、char
等)。
當滿足下列條件時,則認為一個Buffer
小於另一個Buffer
。
- 第一個不相等的元素小於另一個
Buffer
中對應的元素。 - 所有元素都相等,但第一個
Buffer
比另一個先耗盡(第一個Buffer
的元素個數比另一個少)。
總結
這裏只是對Buffer
進行了入門的介紹,具體深入學習還需要查看各種緩沖區以及相關的具體實現。
歡迎關註技術公眾號: 零壹技術棧
本帳號將持續分享後端技術幹貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分布式和微服務,架構學習和進階等學習資料和文章。
Java NIO系列(二) - Buffer