天天看點

關于 Java NIO Buffer 使用的詳細解讀Buffer類型配置設定一個Buffer将資料寫入Buffer

在與NIO通道互動時使用Java NIO Buffer。 如您所知,資料從通道讀入緩沖區,并從緩沖區寫入通道。

緩沖區本質上是一個可以寫入資料的記憶體塊,然後可以再次讀取。 此記憶體塊包含在NIO Buffer對象中,該對象提供了一組方法,可以更輕松地使用記憶體塊。

基本緩沖區用法

使用緩沖區讀取和寫入資料通常遵循這4個小步驟:

  1. 寫入資料到緩沖區
  2. 調用 buffer.flip()
  3. 從緩沖區讀取資料
  4. 調用 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 的含義無論讀寫模式都是相同的。

下面是對以上三個屬性在讀模式和寫模式的一個示例,後面會有詳細的解釋:

關于 Java NIO Buffer 使用的詳細解讀Buffer類型配置設定一個Buffer将資料寫入Buffer

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:

  1. 從Channel将資料寫入Buffer
  2. 調用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 中讀取資料。

  1. 從緩沖區讀取資料到通道。
  2. 使用緩沖區自帶方法中的 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()

兩個緩沖相同,如果:

  1. 他們是同一個類型(byte,char,int等)。
  2. 在緩沖區,它們遺留有相同量的位元組、字元等。
  3. 所有遺留的位元組、字元都相同。

正如你看到的,equals隻比較Buffer的一部分,而不是每個元素。事實上,它隻比較Buffer中遺留的元素。

compareTo()

用 compareTo() 方法比較兩個緩沖區的遺留元素(位元組、字元等),用在例如排序例程。在下列情況中,一個緩沖區被視為“小于”另一個緩沖區,如果:

  1. 與另一個緩沖區對應元素相等的第一個元素,小于另一個緩沖區的元素。
  2. 所有的元素都相等,但是第一個緩沖區在第二個緩沖區之前耗盡了元素(它有更少的元素)。

本文來自雲栖社群合作夥伴“開源中國”

本文作者:達爾文

原文連結