一、Buffer(緩沖區)介紹
Java NIO Buffers用于和NIO Channel交互。 我們從Channel中讀取數據到buffers里,從Buffer把數據寫入到Channels.
Buffer本質上就是一塊內存區,可以用來寫入數據,并在稍后讀取出來。這塊內存被NIO Buffer包裹起來,對外提供一系列的讀寫方便開發的接口。
在Java NIO中使用的核心緩沖區如下(覆蓋了通過I/O發送的基本數據類型:byte, char、short, int, long, float, double ,long):
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
FloatBuffer
DoubleBuffer
LongBuffer
(1)利用Buffer讀取數據,通常經過如下四個步驟:
1.把數據寫入buffer;
2.調用flip;
3.從Buffer中讀取數據;
4.調用buffer.clear()或者buffer.compact()
當寫入數據到buffer中時,buffer會記錄已經寫入的數據大小。當需要讀數據時,通過 flip() 方法把buffer從寫模式調整為讀模式;在讀模式下,可以讀取所有已經寫入的數據。
當讀取完數據后,需要清空buffer,以滿足后續寫入操作。清空buffer有兩種方式:調用 clear() 或 compact() 方法。clear會清空整個buffer,compact則只清空已讀取的數據,未被讀取的數據會被移動到buffer的開始位置,寫入位置則近跟著未讀數據之后。
(2)Buffer的容量,位置,上限(Buffer Capacity, Position and Limit)
Buffer緩沖區實質上就是一塊內存,用于寫入數據,也供后續再次讀取數據。這塊內存被NIO Buffer管理,并提供一系列的方法用于更簡單的操作這塊內存。
一個Buffer有三個屬性是必須掌握的,分別是:
capacity容量
position位置
limit限制
position和limit的具體含義取決于當前buffer的模式。capacity在兩種模式下都表示容量。
讀寫模式下position和limit的含義:
1)容量
作為一塊內存,buffer有一個固定的大小,叫做capacit(容量)。也就是最多只能寫入容量值得字節,整形等數據。一旦buffer寫滿了就需要清空已讀數據以便下次繼續寫入新的數據。
2)位置
當寫入數據到Buffer的時候需要從一個確定的位置開始,默認初始化時這個位置position為0,一旦寫入了數據比如一個字節,整形數據,那么position的值就會指向數據之后的一個單元,position最大可以到capacity-1.
當從Buffer讀取數據時,也需要從一個確定的位置開始。buffer從寫入模式變為讀取模式時,position會歸零,每次讀取后,position向后移動。
3)上限
在寫模式,limit的含義是我們所能寫入的最大數據量,它等同于buffer的容量。
一旦切換到讀模式,limit則代表我們所能讀取的最大數據量,他的值等同于寫模式下position的位置。換句話說,您可以讀取與寫入數量相同的字節數(限制設置為寫入的字節數,由位置標記)。
二、Buffer的常用方法
方法 介紹
abstract Object array() 返回支持此緩沖區的數組 (可選操作)
abstract int arrayOffset() 返回該緩沖區的緩沖區的第一個元素的在數組中的偏移量 (可選操作)
int capacity() 返回此緩沖區的容量
Buffer clear() 清除此緩存區。將position = 0;limit = capacity;mark = -1;
Buffer flip() flip()方法可以吧Buffer從寫模式切換到讀模式。調用flip方法會把position歸零,并設置limit為之前的position的值。 也就是說,現在position代表的是讀取位置,limit標示的是已寫入的數據位置。
abstract boolean hasArray() 告訴這個緩沖區是否由可訪問的數組支持
boolean hasRemaining() return position < limit,返回是否還有未讀內容
abstract boolean isDirect() 判斷個緩沖區是否為 direct
abstract boolean isReadOnly() 判斷告知這個緩沖區是否是只讀的
int limit() 返回此緩沖區的限制
Buffer position(int newPosition) 設置這個緩沖區的位置
int remaining() return limit - position; 返回limit和position之間相對位置差
Buffer rewind() 把position設為0,mark設為-1,不改變limit的值
Buffer mark() 將此緩沖區的標記設置在其位置
三、Buffer的使用方式/方法介紹
(1)分配緩沖區(Allocating a Buffer)
為了獲得緩沖區對象,我們必須首先分配一個緩沖區。在每個Buffer類中,allocate()方法用于分配緩沖區。
ByteBuffer byteBuffer=ByteBuffer.allocate(30);
CharBuffer charBuffer=CharBuffer.allocate(130);
上面是ByteBuffer分配30字節和CharBuffer分配130字節的例子。
(2)寫入數據到緩沖區(Writing Data to a Buffer)
寫數據到Buffer有兩種方式:從Channel中寫入到Buffer 手動寫數據到Buffer,調用put方法
int bytesRead =inChannel.read(buf);//read into buffer.
buf.put(127);
put方法有很多不同版本,根據文檔說明進行調用即可。
(3)翻轉(flip())
flip()方法可以吧Buffer從寫模式切換到讀模式。調用flip方法會把position歸零,并設置limit為之前的position的值。 也就是說,現在position代表的是讀取位置,limit標示的是已寫入的數據位置。
(4)從Buffer讀取數據(Reading Data from a Buffer)
從Buffer讀數據也有兩種方式:1.從buffer讀數據到channel 2.從buffer直接讀取數據,調用get方法
讀取數據到channel的例子:
int bytesWritten=inChannel.write(buf);
調用get讀取數據的例子:
byte aByte=buf.get();
get也有諸多版本,對應了不同的讀取方式。
(5)rewind()
Buffer.rewind()方法將position置為0,這樣我們可以重復讀取buffer中的數據。limit保持不變。
(6)clear() and compact()
一旦我們從buffer中讀取完數據,需要復用buffer為下次寫數據做準備。只需要調用clear()或compact()方法。
如果調用的是clear()方法,position將被設回0,limit被設置成 capacity的值。換句話說,Buffer 被清空了。Buffer中的數據并未清除,只是這些標記告訴我們可以從哪里開始往Buffer里寫數據。
如果Buffer還有一些數據沒有讀取完,調用clear就會導致這部分數據被“遺忘”,因為我們沒有標記這部分數據未讀。
針對這種情況,如果需要保留未讀數據,那么可以使用compact。 因此 compact() 和 clear() 的區別就在于: 對未讀數據的處理,是保留這部分數據還是一起清空 。
(7)mark()與reset()方法
通過調用Buffer.mark()方法,可以標記Buffer中的一個特定position。之后可以通過調用Buffer.reset()方法恢復到這個position。
(8)equals() and compareTo()
可以用eqauls和compareTo比較兩個buffer
equals():
判斷兩個buffer相對,需滿足:
類型相同
buffer中剩余字節數相同
所有剩余字節相等
從上面的三個條件可以看出,equals只比較buffer中的部分內容,并不會去比較每一個元素。
compareTo():
compareTo也是比較buffer中的剩余元素,只不過這個方法適用于比較排序的:
四、四 Buffer常用方法測試
這里一位ByteBuffer為例,介紹一下常用的用法。
//分配緩沖區
ByteBuffer byteBuffer=ByteBuffer.allocate(44);
System.out.println("----Reset----");
//clear()方法 position設置為0,limit設置為capality
byteBuffer.clear();
//設置緩沖區的位置
byteBuffer.position(5);
byteBuffer.mark();
byteBuffer.position(10);
System.out.println("Before reset:"+byteBuffer);
byteBuffer.reset();
System.out.println("After reset:"+byteBuffer);
System.out.println("----Rewind----");
byteBuffer.position(10);
//限制緩沖區的大小
byteBuffer.limit(15);
System.out.println("Before rewind:"+byteBuffer);
//把position設為0,mark設為-1,不改變limit的值
byteBuffer.rewind();
System.out.println("After rewind:"+byteBuffer);
System.out.println("----Com----");
byteBuffer.clear();
byteBuffer.put("abcd".getBytes());
System.out.println("Before compact:"+byteBuffer);
System.out.println(new String(byteBuffer.array()));
//limit = position;position = 0;mark = -1; 翻轉,也就是讓flip之后的position到limit這塊區域變成之前的0到position這塊,
//翻轉就是將一個處于存數據狀態的緩沖區變為一個處于準備取數據的狀態
byteBuffer.flip();
System.out.println("After flip:"+byteBuffer);
System.out.println("----Get----");
//get()方法:相對讀,從position位置讀取一個byte,并將position+1,為下次作準備
System.out.println((char)byteBuffer.get());
System.out.println((char)byteBuffer.get());
//System.out.println((char)byteBuffer.get());
System.out.println("After three gets:"+byteBuffer);
System.out.println("\t"+new String(byteBuffer.array()));
//把從position到limit中的內容移到0到limit-position的區域內,position和limit的取值也分別變成limit-position、capacity。
// 如果先將positon設置到limit,再compact,那么相當于clear()
byteBuffer.compact();
System.out.println("After compact:"+byteBuffer);
System.out.println(new String(byteBuffer.array()));