java.nio.ByteBuffer ------------------------------- Capacity 緩沖區最大容量 Limit 當前最大使用量,或者說是有效數據的EOF位置。 Position 指向當前位置的指針 ----------------------------------- 假設一個緩沖區容量是10,開始指針指向0,即position=0。 然后寫入6個字節數據,寫完后,下標0、1、2、3、4、5有數據, 指針指向6,即當前position=6。 此時,用limit(6)方法將當前位置設為EOF位置。 那么,讀數據的時候,讀到EOF位置就結束了。 下標超過的話,會報錯java.nio.BufferUnderflowException ------------------------------------- clear(),只是把指針移到位置0,并沒有真正清空數據。 flip(),當前位置設置為EOF,指針指向0. rewind,指針指向0. compact(),壓縮數據。比如當前EOF是6,當前指針指向2 (即0,1的數據已經寫出了,沒用了), 那么compact方法將把2,3,4,5的數據挪到0,1,2,3的位置, 然后指針指向4的位置。這樣的意思是,從4的位置接著再寫入數據。 寫完后,把指針挪到0,再寫出,然后再compact(),如此反復…… -------------------------------- buf.clear(); // 清空一下,準備 for (;;) { if (in.read(buf) < 0 && !buf.hasRemaining()) break; // 沒有讀入數據了,并且buffer里沒有剩余數據了 buf.flip(); //當前位置設置為EOF,指針挪到0 out.write(buf); //寫出數據,即讀取buffer的數據 buf.compact(); // write方法可能只寫出了部分數據,buffer里還有剩余。 //壓縮一下,把后一段的數據挪到前面。指針也挪到有效數據的后一位。 } -------------------------- 下面是一段測試小代碼,有助于熟悉各方法:
import java.nio.ByteBuffer;
public class ByteBufferTest {
public static void main(String[] args) {
//10個字節大小 ByteBuffer buffer = ByteBuffer.allocate(10); //容量是10,EOF位置是10,初始位置也是0 v(buffer.capacity()); v(buffer.limit()); //輸出看看,輸出是10個0 printBuffer(buffer); //此時,指針指向位置10,已經是最大容量了。 //把指針挪回位置1 buffer.rewind(); //寫操作,指針會自動移動 buffer.putChar('a'); v(buffer.position()); //指針指向2 buffer.putChar('啊'); v(buffer.position()); //指針指向4 //當前位置設置為EOF,指針挪回位置1 //相當于下面兩句: //buffer.limit(4); //buffer.position(0); buffer.flip(); //輸出前4個字節看看,輸出是0 61 55 4a printBuffer(buffer); //指針挪到位置1,壓縮一下 //輸出是61 55 4a 4a 0 0 0 0 0 0 //compact方法會把EOF位置重置為最大容量,這里就是10 buffer.position(1); buffer.compact(); printBuffer(buffer); //注意當前指針指向3,繼續寫入數據的話,就會覆蓋后面的數據了。 v(buffer.position()); } /** * 輸出buffer內容. */ public static void printBuffer(ByteBuffer buffer){ //記住當前位置 int p = buffer.position(); //指針挪到0 buffer.position(0); //循環輸出每個字節內容 for(int i=0;i<buffer.limit();i++){ byte b = buffer.get(); //讀操作,指針會自動移動 v(Integer.toHexString(b)); } //指針再挪回去 buffer.position(p); //本想用mark()和reset()來實現。 //但是,它們貌似只能正向使用。 //如,位置6的時候,做一下Mark, //然后在位置10(位置要大于6)的時候,用reset就會跳回位置6. //而position(n)這個方法,如果之前做了Mark,但是Mark位置大于新位置,Mark會被清除。 //也就是說,做了Mark后,只能向前跳,不能往回跳,否則Mark就丟失。 //rewind()方法,更干脆,直接清除mark。 //flip()方法,也清除mark //clear()方法,也清除mark //compact方法,也清除mark //所以,mark方法干脆不要用了,自己拿變量記一下就完了。 } public static void v(Object o){ System.out.println(o); }
}
狀態變量 可以用三個值指定緩沖區在任意時刻的狀態: 這三個變量一起可以跟蹤緩沖區的狀態和它所包含的數據。我們將在下面的小節中詳細分析每一個變量,還要介紹它們如何適應典型的讀/寫(輸入/輸出)進程。在這個例子中,我們假定要將數據從一個輸入通道拷貝到一個輸出通道。 回頁首 Position 您可以回想一下,緩沖區實際上就是美化了的數組。在從通道讀取時,您將所讀取的數據放到底層的數組中。position 變量跟蹤已經寫了多少數據。更準確地說,它指定了下一個字節將放到數組的哪一個元素中。因此,如果您從通道中讀三個字節到緩沖區中,那么緩沖區的 position 將會設置為3,指向數組中第四個元素。 同樣,在寫入通道時,您是從緩沖區中獲取數據。 position 值跟蹤從緩沖區中獲取了多少數據。更準確地說,它指定下一個字節來自數組的哪一個元素。因此如果從緩沖區寫了5個字節到通道中,那么緩沖區的 position 將被設置為5,指向數組的第六個元素。 回頁首 Limit limit 變量表明還有多少數據需要取出(在從緩沖區寫入通道時),或者還有多少空間可以放入數據(在從通道讀入緩沖區時)。
position 總是小于或者等于 limit 。
回頁首 Capacity 緩沖區的 capacity 表明可以儲存在緩沖區中的最大數據容量。實際上,它指定了底層數組的大小 ― 或者至少是指定了準許我們使用的底層數組的容量。 limit 決不能大于 capacity 。
回頁首 觀察變量 我們首先觀察一個新創建的緩沖區。出于本例子的需要,我們假設這個緩沖區的 總容量 為8個字節。 Buffer 的狀態如下所示:
回想一下 ,limit 決不能大于 capacity ,此例中這兩個值都被設置為 8。我們通過將它們指向數組的尾部之后(如果有第8個槽,則是第8個槽所在的位置)來說明這點。
position 設置為0。如果我們讀一些數據到緩沖區中,那么下一個讀取的數據就進入 slot 0 。如果我們從緩沖區寫一些數據,從緩沖區讀取的下一個字節就來自 slot 0 。 position 設置如下所示:
由于 capacity 不會改變,所以我們在下面的討論中可以忽略它。 回頁首 第一次讀取 現在我們可以開始在新創建的緩沖區上進行讀/寫操作。首先從輸入通道中讀一些數據到緩沖區中。第一次讀取得到三個字節。它們被放到數組中從 position 開始的位置,這時 position 被設置為 0。讀完之后,position 就增加到 3,如下所示:
limit 沒有改變。
回頁首 第二次讀取 在第二次讀取時,我們從輸入通道讀取另外兩個字節到緩沖區中。這兩個字節儲存在由 position 所指定的位置上, position 因而增加 2:
limit 沒有改變。
回頁首 flip 現在我們要將數據寫到輸出通道中。在這之前,我們必須調用 flip() 方法。這個方法做兩件非常重要的事: - 它將
limit 設置為當前 position 。 - 它將
position 設置為 0。
前一小節中的圖顯示了在 flip 之前緩沖區的情況。下面是在 flip 之后的緩沖區:
我們現在可以將數據從緩沖區寫入通道了。 position 被設置為 0,這意味著我們得到的下一個字節是第一個字節。 limit 已被設置為原來的 position ,這意味著它包括以前讀到的所有字節,并且一個字節也不多。 回頁首 第一次寫入 在第一次寫入時,我們從緩沖區中取四個字節并將它們寫入輸出通道。這使得 position 增加到 4,而 limit 不變,如下所示:
回頁首 第二次寫入 我們只剩下一個字節可寫了。 limit 在我們調用 flip() 時被設置為 5,并且 position 不能超過 limit 。所以最后一次寫入操作從緩沖區取出一個字節并將它寫入輸出通道。這使得 position 增加到 5,并保持 limit 不變,如下所示:
回頁首 clear 最后一步是調用緩沖區的 clear() 方法。這個方法重設緩沖區以便接收更多的字節。 Clear 做兩種非常重要的事情: - 它將
limit 設置為與 capacity 相同。 - 它設置
position 為 0。
下圖顯示了在調用 clear() 后緩沖區的狀態:
緩沖區現在可以接收新的數據了。 回頁首 訪問方法 到目前為止,我們只是使用緩沖區將數據從一個通道轉移到另一個通道。然而,程序經常需要直接處理數據。例如,您可能需要將用戶數據保存到磁盤。在這種情況下,您必須將這些數據直接放入緩沖區,然后用通道將緩沖區寫入磁盤。 或者,您可能想要從磁盤讀取用戶數據。在這種情況下,您要將數據從通道讀到緩沖區中,然后檢查緩沖區中的數據。 在本節的最后,我們將詳細分析如何使用 ByteBuffer 類的 get() 和 put() 方法直接訪問緩沖區中的數據。 回頁首 get() 方法 ByteBuffer 類中有四個 get() 方法:
byte get(); ByteBuffer get( byte dst[] ); ByteBuffer get( byte dst[], int offset, int length ); byte get( int index );
第一個方法獲取單個字節。第二和第三個方法將一組字節讀到一個數組中。第四個方法從緩沖區中的特定位置獲取字節。那些返回 ByteBuffer 的方法只是返回調用它們的緩沖區的 this 值。 此外,我們認為前三個 get() 方法是相對的,而最后一個方法是絕對的。 相對 意味著 get() 操作服從 limit 和position 值 ― 更明確地說,字節是從當前 position 讀取的,而 position 在 get 之后會增加。另一方面,一個絕對 方法會忽略 limit 和 position 值,也不會影響它們。事實上,它完全繞過了緩沖區的統計方法。 上面列出的方法對應于 ByteBuffer 類。其他類有等價的 get() 方法,這些方法除了不是處理字節外,其它方面是是完全一樣的,它們處理的是與該緩沖區類相適應的類型。 回頁首 put()方法 ByteBuffer 類中有五個 put() 方法:
ByteBuffer put( byte b ); ByteBuffer put( byte src[] ); ByteBuffer put( byte src[], int offset, int length ); ByteBuffer put( ByteBuffer src ); ByteBuffer put( int index, byte b );
第一個方法 寫入(put) 單個字節。第二和第三個方法寫入來自一個數組的一組字節。第四個方法將數據從一個給定的源 ByteBuffer 寫入這個 ByteBuffer 。第五個方法將字節寫入緩沖區中特定的 位置 。那些返回 ByteBuffer 的方法只是返回調用它們的緩沖區的 this 值。 與 get() 方法一樣,我們將把 put() 方法劃分為 相對 或者 絕對 的。前四個方法是相對的,而第五個方法是絕對的。 上面顯示的方法對應于 ByteBuffer 類。其他類有等價的 put() 方法,這些方法除了不是處理字節之外,其它方面是完全一樣的。它們處理的是與該緩沖區類相適應的類型。 回頁首 類型化的 get() 和 put() 方法 除了前些小節中描述的 get() 和 put() 方法, ByteBuffer 還有用于讀寫不同類型的值的其他方法,如下所示: getByte() getChar() getShort() getInt() getLong() getFloat() getDouble() putByte() putChar() putShort() putInt() putLong() putFloat() putDouble()
事實上,這其中的每個方法都有兩種類型 ― 一種是相對的,另一種是絕對的。它們對于讀取格式化的二進制數據(如圖像文件的頭部)很有用。 您可以在例子程序 TypesInByteBuffer.java 中看到這些方法的實際應用。 回頁首 緩沖區的使用:一個內部循環 下面的內部循環概括了使用緩沖區將數據從輸入通道拷貝到輸出通道的過程。 while (true) { buffer.clear(); int r = fcin.read( buffer ); if (r==-1) { break; } buffer.flip(); fcout.write( buffer ); } |
read() 和 write() 調用得到了極大的簡化,因為許多工作細節都由緩沖區完成了。 clear() 和 flip() 方法用于讓緩沖區在讀和寫之間切換。
|