關於 Java NIO Buffer 使用的詳細解讀
在與IO/">NIO通道互動時使用Java NIO Buffer。 如您所知,資料從通道讀入緩衝區,並從緩衝區寫入通道。
緩衝區本質上是一個可以寫入資料的記憶體塊,然後可以再次讀取。 此記憶體塊包含在NIO Buffer物件中,該物件提供了一組方法,可以更輕鬆地使用記憶體塊。
基本緩衝區用法
使用緩衝區讀取和寫入資料通常遵循這4個小步驟:
-
寫入資料到緩衝區
-
呼叫 buffer.flip()
-
從緩衝區讀取資料
-
呼叫 buffer.clear() 或者 buffer.compact()
當你將資料寫入Buffer時,Buffer會跟蹤你已經寫入了多少資料。一旦你需要讀出資料,你需要呼叫 flip() 方法將Buffer從寫模式轉換到讀模式。在讀模式,Buffer允許你將之前寫入的資料全部讀出。
一旦你已經讀出了所有資料,你需要清除Buffer,為下次寫入資料做準備。可以通過以下兩種方法來完成:clear() 和 compact()。clear() 方法清除整個Buffer,而 compact() 方法僅僅清除你已經讀出的Buffer,未讀資料會被移動到Buffer的開始位置,再次寫入的資料會追加到未讀資料的後面。
這是一個簡單的 Buffer 使用的例子,使用的 write, flip, read 和 clear 操作:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); FileChannel inChannel = aFile.getChannel(); //create buffer with capacity of 48 bytes ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf); //read into buffer. while (bytesRead != -1) {buf.flip();//make buffer ready for read while(buf.hasRemaining()){ System.out.print((char) buf.get()); // read 1 byte at a time }buf.clear(); //make buffer ready for writing bytesRead = inChannel.read(buf); } aFile.close();
Buffer的三個屬性:容量,位置和限定符
Buffer本質上是一塊你可以寫入資料的記憶體區域,當然你也可以在寫入之後讀出資料。該記憶體區域被封裝成一個 NIO Buffer 物件,它提供一系列的方法,以方便對該記憶體區域的操作。
為了學習Buffer 是如何工作的,Buffer 的三個屬性你必須要熟悉,它們是:
-
capacity (容量)
-
position (遊標位置)
-
limit (末尾限定符)
其中,position 和 limit 的意義依賴於當前 Buffer 是處於讀模式還是寫模式。capacity 的含義無論讀寫模式都是相同的。
下面是對以上三個屬性在讀模式和寫模式的一個示例,後面會有詳細的解釋:
Buffer capacity, position and limit in write and read mode.
Capacity (容量)
作為一個記憶體塊,Buffer 有一個固定的大小,我們叫做 “capacity(容量)"。你最多隻能向 Buffer 寫入 capacity 大小的位元組,長整數,字元等。一旦 Buffer 滿了,你必須在繼續寫入資料之前清空它(讀出資料,或清除資料)。
Position (遊標位置)
當你開始向 Buffer 寫入資料時,你必須知道資料將要寫入的位置。position 的初始值為 0。當一個位元組或長整數等類似資料型別被寫入 Buffer 後,position 就會指向下一個將要寫入資料的位置(根據資料型別大小計算)。position 的最大值是 capacity - 1。
當你需要從 Buffer 讀出資料時,你也需要知道將要從什麼位置開始讀資料。在你呼叫 flip 方法將 Buffer 從寫模式轉換為讀模式時,position 被重新設定為 0。然後你從 position 指向的位置開始讀取資料,接下來 position 指向下一個你要讀取的位置。
限制(Limit)
在寫模式下對一個Buffer的限制即你能將多少資料寫入Buffer中。在寫模式下,限制等同於Buffer的容量(capacity)。
當切換Buffer為讀模式時,限制表示你最多能讀取到多少資料。因此,當切換Buffer為讀模式時,限制會被設定為寫模式下的position值。換句話說,你能讀到之前寫入的所有資料(限制被設定為已寫的位元組數,在寫模式下就是position)。
Buffer型別
Java NIO提出瞭如下幾種Buffer型別:
-
ByteBuffer
-
MappedByteBuffer
-
CharBuffer
-
DoubleBuffer
-
FloatBuffer
-
IntBuffer
-
LongBuffer
-
ShortBuffer
正如你所看到的,這些Buffer型別代表了不同的資料型別。換句話說,他們讓你可以在使用的時候用char, short, int, long, float 或者double型別來代替直接使用buffer中的位元組。
其中MappedByteBuffer有點特殊,將在它自己的部分來闡述。
分配一個Buffer
若要獲取一個Buffer物件,必須先分配它。每個Buffer類都有allocate()函式用來分配。下面的例子展示了分配一個ByteBuffer,其容量為48位元組。
ByteBuffer buf = ByteBuffer.allocate(48);
下面的例子是分配一個具有1024個字元的空間的CharBuffer:
CharBuffer buf = CharBuffer.allocate(1024);
將資料寫入Buffer
有兩種方法可以將資料寫入Buffer:
-
從Channel將資料寫入Buffer
-
呼叫buffer的put()函式,自己將資料寫入Buffer。
下面的例子是展示Channel如何將資料寫入到Buffer中:
int bytesRead = inChannel.read(buf); //read into buffer.
下面的例子是通過put()函式將資料寫入Buffer:
buf.put(127);
put()函式還有很多其他版本,可以讓你使用不用的方法將資料寫入到Buffer。例如,在特定的位置寫,或者將位元組陣列寫入到buffer。檢視JavaDoc來了解buffer實現的更多細節。
flip()
用 flip() 方法將 Buffer f從寫入模式切換到讀取模式。呼叫 flip() 將 position 設定回0,並將 limit 置為剛才的位置。
換句話說,position 現在標記了讀取位置,limit 標記了寫入緩衝區的位元組、字元數等——可以讀取的位元組數、字元數等的限制。
從緩衝區讀取資料
有兩種方法可以從 Buffer 中讀取資料。
-
從緩衝區讀取資料到通道。
-
使用緩衝區自帶方法中的 get() 方法從緩衝區讀取資料。
下面是如何將資料從緩衝區讀取到通道的例子:
//read from buffer into channel. int bytesWritten = inChannel.write(buf);
下面是使用 get() 方法從 Buffer 中讀取資料的例子:
byte aByte = buf.get();
get() 方法還有許多其他版本,允許您以多種不同的方式從 Buffer 中讀取資料。 例如,在特定位置讀取,或者從緩衝區讀取位元組陣列。有關具體緩衝區實現的詳細資訊,請參閱JavaDoc。
rewind()
Buffer.rewind() 將 position 設定回0,因此你可以重讀緩衝區中的所有資料。這個 limit 保持不變,因此仍然標記有多少元素(位元組、字元等)可以從Buffer讀取。
clear() and compact()
從 Buffer 中讀取資料之後,必須讓 Buffer 為再次寫入做好準備。您可以通過呼叫 clear() 或呼叫 compact()來做到這一點。
如果您呼叫 clear() ,position 將被設定為0,並 limit 置為 capacity。換句話說,Buffer 被清除。Buffer 中的資料沒有清除。只有標記告訴您可以將資料寫入 Buffer 的位置。
當您呼叫 clear() 時,如果緩衝區中有任何未讀資料,則資料將被“遺忘”,這意味著您不再有任何標記來說明哪些資料已被讀取,哪些資料未被讀取。
如果 Buffer 中仍然有未讀資料,並且您希望稍後讀取它,但是您需要先寫一些東西,那麼呼叫 compact() 而不是 clear().
compact() 將所有未讀的資料複製到 Buffer 的開頭。然後將 position 設定為最後一個未讀元素之後的位置。與 clear() 一樣,limit 屬性仍然設定為 capacity。現在 Buffer 已經準備好寫入,但是不會覆蓋未讀資料。
mark() and reset()
你可以呼叫 Buffer.mark() 方法在 Buffer 中標記給定位置。之後,你可以呼叫 Buffer.reset() 方法重置回標記的這個位置。下面是個例子:
buffer.mark(); //call buffer.get() a couple of times, e.g. during parsing. buffer.reset();//set position back to mark.
equals() and compareTo()
用equals() 和 compareTo()方法可以比較兩個緩衝區。
equals()
兩個緩衝相同,如果:
-
他們是同一個型別(byte,char,int等)。
-
在緩衝區,它們遺留有相同量的位元組、字元等。
-
所有遺留的位元組、字元都相同。
正如你看到的,equals只比較Buffer的一部分,而不是每個元素。事實上,它只比較Buffer中遺留的元素。
compareTo()
用 compareTo() 方法比較兩個緩衝區的遺留元素(位元組、字元等),用在例如排序例程。在下列情況中,一個緩衝區被視為“小於”另一個緩衝區,如果:
-
與另一個緩衝區對應元素相等的第一個元素,小於另一個緩衝區的元素。
-
所有的元素都相等,但是第一個緩衝區在第二個緩衝區之前耗盡了元素(它有更少的元素)。