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