Java網絡編程從入門到精通(34):讀寫緩沖區中的數據---使用get和put方法按順序讀寫單個數據
對於緩沖區來說,最重要的操作就是讀寫操作。緩沖區提供了兩種方法來讀寫緩沖區中的數據:get、put方法和array方法。而get、put方法可以有三種讀寫數據的方式:按順序讀寫單個數據、在指定位置讀寫單個數據和讀寫數據塊。除了上述的幾種讀寫數據的方法外,CharBuffer類還提供了用於專門寫字符串的put和append方法。在本文及後面的文章中將分別介紹這些讀寫緩沖區的方法。
雖然使用allocate方法創建的緩沖區並不是一次性地分配內存空間,但我們可以從用戶地角度將一個緩沖區想象成一個長度為capacity的數組。當緩沖區創建後,和數組一樣,緩沖區的大小(capacity值)將無法改變,也無法訪問緩沖區外的數據。如下面的代碼創建了一個大小為6的字節緩沖區。
ByteBuffer byteBuffer = ByteBuffer.allocate(6);
對於byteBuffer來說,只能訪問屬於這個緩沖區的六個字節的數據,如果超過了這個范圍,將拋出一個BufferOverflowException異常,這是一個運行時錯誤,因為這個錯誤只能在程序運行時被發現。
既然緩沖區和數組類似,那麼緩沖區也應該象數組一樣可以標識當前的位置。緩沖區的position方法為我們提供了這個功能。position方法有兩種重載形式,它們的定義如下:
public final int position()
public final Buffer position(int newPosition)
第一個重載形式用來獲取緩沖區的當前位置。在創建緩沖區後,position的初始值是0,也就是緩沖區第一個元素的位置。當從緩沖區讀取一個元素後,position的值加1。我們從這一點可以看出,position方法返回的位置就是當前可以讀取的元素的位置。position的取值范圍從0到capacity – 1。如果position的值等於capacity,說明緩沖區當前已經沒有數據可讀了。
position方法的第二個重載形式可以設置緩沖區的當前位置。參數newPosition的取值范圍是0 <= newPosition < capacity。如果newPosition的值超出這個范圍,position方法就會拋出一個IllegalArgumentException異常。
在大多數情況下不需要直接控制緩沖區的位置。緩沖區類提供的用於讀寫數據的方法可以自動地設置緩沖區的當前位置。在緩沖區類中,get和put方法用於讀寫緩沖區中的數據。get和put方法的定義如下:
ByteBuffer類的get和put方法:
public abstract byte get()
public abstract ByteBuffer put(byte b)
IntBuffer類的get和put方法:
public abstract int get()
public abstract IntBuffer put(int i)
其他五個緩沖區類中的get和put方法定義和上面的定義類似,只是get方法返回相應的數據類型,而put方法的參數是相應的數據類型,並且返回值的類型是相應的緩沖區類。
每當put方法向緩沖區寫入一個數據後,緩沖區的當前位置都會加1。如果緩沖區的當前位置已經等於capacity,調用put方法就會拋出一個java.nio.BufferOverflowException異常。在緩沖區未初賦值的區域將被0填充。使用get方法可以得到緩沖區當前位置的數據,並使緩沖區的當前位置加1。和put方法一樣,在緩沖區當前位置等於capacity時使用get方法也會拋出java.nio.BufferOverflowException異常。緩沖區的初始狀態如圖1所示。
圖1 緩沖區的初始狀態
從圖1可以看出,在緩沖區創建之初,當前的位置和緩沖區中的數據都為0。當使用如下語句向緩沖區中寫入數據後,緩沖區當前狀態如圖2所示。
byteBuffer.put((byte)2);
byteBuffer.put((byte)-1);
圖2 緩沖區的當前狀態
當緩沖區的當前位置如圖3所示時,使用put和get方法將會拋出上述的BufferOverflowException異常。
圖3 當前位置處於緩沖區尾
如果要使用get方法得到緩沖區中的指定數據,必須將緩沖區的當前位置移動到指定的位置,我們可以使用position方法將當前位置移到緩沖區的任何位置。如下面的代碼將圖3所示的緩沖區的當前位置設為2,並用get方法獲得位置2的數據:
byteBuffer.position(2);
System.out.println(byteBuffer.get());
上面的代碼將輸出3。緩沖區的當前位置為除了使用position方法,也可以使用rewind方法將緩沖區的當前位置設為0,rewind方法的定義如下:
public final Buffer rewind()
在圖2所示的緩沖區狀態下調用rewind方法,就會得到如圖4的緩沖區狀態。
圖4 調用rewind方法後的緩沖區狀態
接下來讓我們執行如下語句:
System.out.println(byteBuffer.get());
緩沖區的狀態將如圖5所示。
圖5 調用get方法後的緩沖區狀態
緩沖區除了position和capacity外,還提供了一個標識來限制緩沖區可訪問的范圍。這個標識就是limit。limit和position一樣,在緩沖區類中也提供了兩個重載方法。用於獲得和設置limit的值。limit方法的定義如下:
public final int limit()
public final Buffer limit(int newLimit)
在初始狀態下,緩沖區的limit和capacity值相同。但limit和capacity的區別是limit可以通過limit方法進行設置,而capacity在創建緩沖區時就已經指定了,並且不能改變。(在上面所講的position方法的newPosition參數的取值范圍時曾說是0 <= newPosition < capacity,其實嚴格地說,應是0 <= newPosition < limit)limit的其他性質和capacity一樣。如在圖5所示的緩沖區狀態中將limit的值設為2,就變成了圖6所示的狀態。
圖6 將limit設為2的緩沖區狀態
在這時position的值等於limit,就不能訪問緩沖區的當前數據,也就是說不能使用get和put方法。否則將拋出BufferOverflowException異常。由於使用allocate創建的緩沖區並不是一次性地分配內存空間,因此,可以將緩沖區的capacity設為很大的值,如10M。緩沖區過大可能在某些環境中會使系統性能降低(如在PDA或智能插秧機中),因此,可以使用limit方法根據具體的情況來限定緩沖區的大小。當然,limit還可以表示緩沖區中實際的數據量,這將在後面講解。下面的代碼演示了如何使用limit方法來枚舉緩沖區中的數據:
while(byteBuffer.position() < byteBuffer.limit())
System.out.println(byteBuffer.get());
我們還可以用flip和hasRemaining方法來重寫上面的代碼。flip方法將limit設為緩沖區的當前位置。當limit等於position時,hasRemaining方法返回false,而則返回true。 flip和hasRemaining方法的定義如下:
public final Buffer flip()
public final boolean hasRemaining()
下面的代碼演示了如何使用hasRemaining方法來枚舉緩沖區中的數據:
while(byteBuffer.hasRemaining())
System.out.println(byteBuffer.get());
如果從緩沖區的第一個位置依次使用put方法向緩沖區寫數據,當寫完數據後,再使用flip方法。這樣limit的值就等於緩沖區中實際的數據量了。在網絡中傳遞數據時,可以使用這種方法來設置數據的結束位置。
為了回顧上面所講內容,下面的代碼總結了創建緩沖區、讀寫緩沖區中的數據、設置緩沖區的limit和position的方法。
package net;
import java.nio.*;
public class GetPutData
{
public static void main(String[] args)
{
// 創建緩沖區的四種方式
IntBuffer intBuffer = IntBuffer.allocate(10);
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10);
CharBuffer charBuffer = CharBuffer.wrap("abcdefg");
DoubleBuffer doubleBuffer = DoubleBuffer.wrap(new double[] { 1.1, 2.2 });
// 向緩沖區中寫入數據
intBuffer.put(1000);
intBuffer.put(2000);
System.out.println("intBuffer的當前位置:" + intBuffer.position());
intBuffer.position(1); // 將緩沖區的當前位置設為1
System.out.println(intBuffer.get()); // 輸出緩沖區的當前數據
intBuffer.rewind(); // 將緩沖區的當前位置設為0
System.out.println(intBuffer.get()); // 輸出緩沖區的當前數據
byteBuffer.put((byte)20);
byteBuffer.put((byte)33);
byteBuffer.flip(); // 將limit設為position,在這裡是2
byteBuffer.rewind();
while(byteBuffer.hasRemaining()) // 枚舉byteBuffer中的數據
System.out.print(byteBuffer.get() + " ");
while(charBuffer.hasRemaining()) // 枚舉charBuffer中的數據
System.out.print(charBuffer.get() + " ");
// 枚舉doubleBuffer中的數據
while(doubleBuffer.position() < doubleBuffer.limit())
System.out.print(doubleBuffer.get() + " ");
}
}
運行結果:
intBuffer的當前位置:2
2000
1000
20 33 a b c d e f g 1.1 2.2
注意:如果必須使用緩沖區的大小來讀取緩沖區的數據,盡量不要使用capacity,而要使用limit。如盡量不要寫成如下的代碼:
while(byteBuffer.position() < byteBuffer.capacity())
System.out.println(byteBuffer.get());
這是因為當limit比capacity小時,上面的代碼將會拋出一個BufferUnderflowException異常。