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); } } 可以用三個值指定緩沖區在任意時刻的狀態:
這三個變量一起可以跟蹤緩沖區的狀態和它所包含的數據。我們將在下面的小節中詳細分析每一個變量,還要介紹它們如何適應典型的讀/寫(輸入/輸出)進程。在這個例子中,我們假定要將數據從一個輸入通道拷貝到一個輸出通道。 您可以回想一下,緩沖區實際上就是美化了的數組。在從通道讀取時,您將所讀取的數據放到底層的數組中。 同樣,在寫入通道時,您是從緩沖區中獲取數據。
緩沖區的
我們首先觀察一個新創建的緩沖區。出于本例子的需要,我們假設這個緩沖區的 ![]() 回想一下 , ![]()
![]() 由于 現在我們可以開始在新創建的緩沖區上進行讀/寫操作。首先從輸入通道中讀一些數據到緩沖區中。第一次讀取得到三個字節。它們被放到數組中從 ![]()
在第二次讀取時,我們從輸入通道讀取另外兩個字節到緩沖區中。這兩個字節儲存在由 ![]()
現在我們要將數據寫到輸出通道中。在這之前,我們必須調用
前一小節中的圖顯示了在 flip 之前緩沖區的情況。下面是在 flip 之后的緩沖區: ![]() 我們現在可以將數據從緩沖區寫入通道了。 在第一次寫入時,我們從緩沖區中取四個字節并將它們寫入輸出通道。這使得 ![]() 我們只剩下一個字節可寫了。 ![]() 最后一步是調用緩沖區的
下圖顯示了在調用 ![]() 緩沖區現在可以接收新的數據了。 到目前為止,我們只是使用緩沖區將數據從一個通道轉移到另一個通道。然而,程序經常需要直接處理數據。例如,您可能需要將用戶數據保存到磁盤。在這種情況下,您必須將這些數據直接放入緩沖區,然后用通道將緩沖區寫入磁盤。 或者,您可能想要從磁盤讀取用戶數據。在這種情況下,您要將數據從通道讀到緩沖區中,然后檢查緩沖區中的數據。 在本節的最后,我們將詳細分析如何使用
第一個方法獲取單個字節。第二和第三個方法將一組字節讀到一個數組中。第四個方法從緩沖區中的特定位置獲取字節。那些返回 此外,我們認為前三個 上面列出的方法對應于
第一個方法 與 上面顯示的方法對應于 除了前些小節中描述的
事實上,這其中的每個方法都有兩種類型 ― 一種是相對的,另一種是絕對的。它們對于讀取格式化的二進制數據(如圖像文件的頭部)很有用。 您可以在例子程序 TypesInByteBuffer.java 中看到這些方法的實際應用。 下面的內部循環概括了使用緩沖區將數據從輸入通道拷貝到輸出通道的過程。
| |||