一個 Buffer 其實就是一塊內存區域, 我們可以在這個內存區域中進行數據的讀寫. NIO Buffer 其實是這樣的內存塊的一個封裝, 並提供了一些操作方法讓我們能夠方便地進行數據的讀寫.
緩衝區buffer主要是和通道數據交互,即從通道中讀入數據到緩衝區,和從緩衝區中把數據寫入到通道中,通過這樣完成對數據的傳輸。
所有緩衝區都有4個屬性:capacity、limit、position、mark,並遵循:capacity>=limit>=position>=mark>=0,下表格是對着4個屬性的解釋:
屬性 | 描述 |
Capacity | 對於現有的一個內存塊,一個Buffer有一個固定的大小,也稱他爲”capacity“。你可以只是寫這個容量的bytes,long,chars等等進入到Buffer。一旦這個buffer滿了,你需要清空他(讀數據或者清空他)在你寫更多的數據進去之前。 |
Limit |
在寫模式下,一個buffer的limit是限制有多少數據可以寫到buffer。在寫模式下,這個limit等於這個buffer的capacity。 當反轉這個buffer進入到讀模式的時候,limit意味着限制有多少數據可以從這個數據中讀取。因此,當反轉一個buffer進入到讀模式的時候,limit就會設置爲寫模式下的寫的position。換句話說,你可以讀取和寫一樣多的字節(limit被設置成被寫字節的數量,這個是被position標記的) |
Position |
當你寫數據進入到Buffer的時候,你可以在一個確定的位置做這個。最初的,這個position的值是0。當一個byte,long型的數據寫入到buffer的時候,這個position將會提前指向到這個buffer下一個單元去插入數據。position的最大值爲capacity - 1。 當你從一個buffer中讀取數據的時候,你也可以從一個給予的position處讀取數據。當你反轉一個buffer從寫模式到讀模式的時候,這個position將會重新置爲0。就像你從一個buffer中指定的位置讀數據是一樣的,並且position提前到下一個位置去讀取。 |
Mark | 標記,調用mark()來設置mark=position,在調用 |
一、創建緩衝區
所有的緩衝區類都不能直接使用new關鍵字實例化,它們都是抽象類,但是它們都有一個用於創建相應實例的靜態工廠方法,以ByteBuffer類爲例子:
//創建一個容量爲10的byte緩衝區
ByteBuffer buff = ByteBuffer.allocate(10);
上面代碼將會從堆空間中分配一個容量大小爲10的byte數組作爲緩衝區的byte數據存儲器。對於其他緩衝區類上面方式也適用,如創建容量爲10的CharBuffer:
//創建一個容量爲10的char緩衝區
CharBuffer buff = CharBuffer.allocate(10);
如果想用一個指定大小的數組作爲緩衝區的數據的存儲器,可以使用wrap()方法:
byte[] bytes = new byte[10];
ByteBuffer buff = ByteBuffer.wrap(bytes);
上面代碼中緩衝區的數據會存放在bytes數組中,bytes數組或buff緩衝區任何一方中數據的改動都會影響另一方。還可以創建指定初始位置(position)和上界(limit)的緩衝區:
//使用一個指定數組作爲緩衝區的存儲器
//並創建一個position=3,limit=8,capacity=10的緩衝區
byte[] bytes = new byte[10];
ByteBuffer buff = ByteBuffer.wrap(bytes, 3, 8);
下圖是新創建的一個容量爲10的字節緩衝區的內存圖:
二、操作緩衝區
1、存取(Buffer.get() & Buffer.put())
使用get()從緩衝區中取數據,使用put()向緩衝區中存數據。
// 創建一個容量爲10的byte數據緩衝區
ByteBuffer buff = ByteBuffer.allocate(10);
// 存入4次數據
buff.put((byte) 'A');
buff.put((byte) 'B');
buff.put((byte) 'C');
buff.put((byte) 'D');
// 翻轉緩衝區
buff.flip();
// 讀取2次數據
System.out.println((char)buff.get());
System.out.println((char)buff.get());
上面有提過緩衝區四個屬性值一定遵循capacity>=limit>=position>=mark>=0,put()時,若position超過limit則會拋出BufferOverflowException;get()時,若position超過limit則會拋出BufferUnderflowException。
下面代碼put()四次後的緩衝區內存示意圖:
上面代碼執行buff.flip()將緩衝區翻轉後的內存示意圖:
上面代碼執兩次get()後的緩衝區內存示意圖:
再向Buffer中讀寫數據時有2個方法也非常有用:
Buffer.remaining():返回從當前位置到上界的數據元素數量;
Buffer.hasRemaining():告訴我們從當前位置到上界是否有數據元素;
2、翻轉(Buffer.flip())
翻轉就是將一個處於存數據狀態的緩衝區變爲一個處於準備取數據的狀態,使用flip()方式實現翻轉。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
rewind()方法與flip()很相似,區別在於rewind()不會影響limit,而flip()會重設limit屬性值:
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
3、壓縮(Buffer.compact())
壓縮就是將已讀取了的數據丟棄,保留未讀取的數據並將保留的數據重新填充到緩衝區的頂部,然後繼續向緩衝區寫入數據。
// 創建一個容量爲10的byte數據緩衝區
ByteBuffer buff = ByteBuffer.allocate(10);
// 填充緩衝區
buff.put((byte)'A');
buff.put((byte)'B');
buff.put((byte)'C');
buff.put((byte)'D');
System.out.println("first put : " + new String(buff.array()));
//翻轉
buff.flip();
//釋放
System.out.println((char)buff.get());
System.out.println((char)buff.get());
//壓縮
buff.compact();
System.out.println("compact after get : " + new String(buff.array()));
//繼續填充
buff.put((byte)'E');
buff.put((byte)'F');
//輸出所有
System.out.println("put after compact : " + new String(buff.array()));
first put : ABCD
A
B
compact after get : CDCD
put after compact : CDEF
clear()和compact()
一旦你已經從Buffer中讀取出數據了,你不得不使得這個buffer再次準備去寫。你可以調用clear()方法或者compact()方法去做這個。
如果你調用clear方法,這個position的值將會被設置爲0,這個limit的值是capacity的值。換句話說,這個buffer被清空了。在這個buffer中的數據還沒有被清空,只是這個標記告訴你在哪個位置可以往buffer裏面寫數據。
當你調用clear方法的時候,這裏有任何未被讀取的數據,這些數據將會”忘掉“,丟失,意味着你不再有任何的標記告訴你已經讀取到什麼數據了,沒有讀取到什麼數據。
如果在buffer中仍然有未讀取的數據,並且你想稍後讀取,但是你需要首先做一些寫的事情,調用compact方法代替clear方法。
compact方法拷貝所有未讀取的數據到buffer的開端。然後設置position的值剛好爲最後未讀取數據元素的後面。這個limit的值仍然等於capacity,就像clear一樣。現在這個buffer準備好寫了,但是你不會覆蓋爲讀取的數據。
4、標記(Buffer.mark())
標記就是記住當前位置(使用mark()方法標記),之後可以將位置恢復到標記處(使用reset()方法恢復),mark()和reset()
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
5、比較兩個緩衝區是否相等
比較兩個緩衝區是否相等有2種方法:equals(Object ob) 和compareTo(ByteBuffer that),這兩個方法都是在Buffer的子類中實現的。
equals比較的兩個緩衝區中的每個值,所以允許不同的Buffer對象進行比較;compareTo有類型限制,ByteBuffer只能和ByteBuffer進行比較;比較兩個緩衝區實際上是比較兩個緩衝區中每個緩衝區position到limit之間(不包括limit)的緩衝值。如下圖: