通俗編程——白話NIO之Buffer
Buffer簡單介紹
Buffer意為緩沖區。其本質上就是是一塊可寫入數據,然後能夠從中讀取數據的內存區域。通過該種方式有助於降低系統開銷和提高外設效率。對於緩沖區我們早有所了解,比方在C中標準I/O中的read,write直接調用系統的輸入輸出。而scanf和printf則借助緩沖區在適當的時候調用read。write操作。在NIO中,為了方便對緩沖區的操作。jAVA設計者將緩沖區封裝為Buffer(實際上就是封裝了基本數據元素的數組),並提供對應的方法對其操作。
在開始之前,首先須要明確下面幾個概念:
- 讀模式和寫模式
- Buffer的capacity
- Buffer的position
- Buffer的limit
讀模式和寫模式
對於一塊內存區而言,將數據寫入該區域的過程稱之為寫模式。反之,稱之為讀模式。
capacity
該內存的大小即數組的的容量稱為Buffer的capacity。一旦Buffer滿了,你須要將其清空才幹繼續寫入數據。
position
Buffer當前遊標的位置稱為position。在寫模式下position表示當前的位置。初始的position值為0.當一個byte、long等數據寫到Buffer後,position會向前移動到下一個可插入數據的Buffer單元。position最大可為capacity – 1。當將Buffer從寫模式切換到讀模式,position會先被重置為0,然後當從Buffer的position處讀取數據時。position向前移動到下一個可讀的位置。
limit
在寫模式下,Buffer的limit表示你最多能往Buffer裏寫多少數據。寫模式下,limit等於Buffer的capacity。當切換Buffer到讀模式時。limit表示你最多能讀到多少數據。因此,當切換Buffer到讀模式時。limit會被設置成寫模式下的position值。
換句話說,你能讀到之前寫入的全部數據(limit被設置成已寫數據的數量,這個值在寫模式下就是position)
我們用一張圖來大體的概括:
Buffer使用
Buffer的使用過程大體遵循下面步驟:
分配緩存大小——>寫數據到Buffer——>調用其filp()——>從Buffer讀取數據——>調用clear()或者compact()。
當向buffer寫入數據時。buffer會記錄下寫了多少數據。
一旦要讀取數據,須要通過flip()方法將Buffer從寫模式切換到讀模式。在讀模式下。能夠讀取之前寫入到buffer的全部數據。一旦讀完了全部的數據。就須要清空緩沖區,讓它能夠再次被寫入。
1. 創建Buffer
通過allocate()或者通過wrap()方法
ByteBuffer byteBuffer=ByteBuffer.allocate(48)
Byte[] bytes=new Byte[1024];
//通過該種方式將生成一個limit=capacity=bytes.length的新緩存區,假設bytes裏含有數據,則會用該數據填充緩沖區。要註意的是通過該種方式創建的ByteBuffer其position初始值是0.
ByteBuffer byteBuffer=ByteBuffer.wrap(bytes);
2. 向Buffer中寫數據
通過Channel的read方法。將數據寫到Buffer
通過Buffer自身的put()方法
int bytesRead=fileChannel.read(byteBuffer);
byteBuffer.put(2);
3. 通過filp()進行模式轉換
flip方法將Buffer從寫模式切換到讀模式。調用flip()方法會首先將limit的值設為當前position的值。然後將position設為0。相當運行了下面語句:
limit=position;
position=0;
換句話說,position如今用於標記讀的位置,limit表示之前寫進了多少個byte、char等 —— 如今能讀取多少個byte、char等。
4. 讀取Buffer的數據
從Buffer讀取數據到Channel
使用Buffer的get()方法讀取Buffer的數據
5. 重讀buffer中的數據
Buffer.rewind()將position設回0,所以你能夠重讀Buffer中的全部數據。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。
6. 清空Buffer
一旦讀完Buffer中的數據,須要讓Buffer準備好再次被寫入。能夠通過clear()或compact()方法來完畢。
假設調用的是clear()方法,position將被設回0,limit被設置成 capacity的值。換句話說,Buffer相當於 被清空了(實際上Buffer中的內容並未真正被清空,此時假設調用rewind()或者設置position=0仍然可讀取舊的數據)。
該方法實際上僅僅是重設了position和limit的值。進而告訴我們能夠從哪裏開始往Buffer裏寫數據。
假設Buffer中有一些未讀的數據,調用clear()方法,數據將“被遺忘”,意味著不再有不論什麽標記會告訴你哪些數據被讀過。哪些還沒有。
假設Buffer中仍有未讀的數據。且興許還須要這些數據,可是此時想要先先寫些數據。那麽使用compact()方法。
compact()方法將全部未讀的數據復制到Buffer起始處。
然後將position設到最後一個未讀元素正後面。limit屬性依舊像clear()方法一樣,設置成capacity。如今Buffer準備好寫數據了,可是不會覆蓋未讀的數據。
7. 通過mark()標記position位置。在進行其它操作後可通過reset() 方法恢復到標記的position
buffer.mark()
...
buffer.reset()
Buffer完整演示樣例
public class testBuffer {
public static void main(String[] args) {
/**
* 分配空間 隱含地在內存中分配了一個byte型數組來存儲10個byte
*/
ByteBuffer buffer = ByteBuffer.allocate(10);
/**
* 填充元素 buffer.hasRemaining()用於推斷緩沖區是否達到上界limit。 該填充過程等效於:int remainCount = buffer.remaining();for (int j = 0; j < remainCount;
* j++){buffer.put((byte) j++);}
*/
int i = 0;
while (buffer.hasRemaining()) {
buffer.put((byte) i++);
}
/**
* 翻轉緩沖區 將緩沖區進行翻轉操作,即在緩沖區寫入完畢時,將緩沖區翻轉成一個準備讀出元素的狀態。 flip操作等效於buffer.limit(buffer.position()).position(0);同一時候將mark設為-1。 源代碼例如以下:public final Buffer
* flip(){ limit = position;position = 0;mark = -1;return this;}
*/
buffer.flip();
/**
* 讀取緩沖區
*/
int remainCount = buffer.remaining();
for (int j = 0; j < remainCount; j++) {
System.out.print(buffer.get() + " ");
}
System.out.println();
/**
* 字節順序
*/
System.out.println("ByteOrder的字節順序為:" + ByteOrder.nativeOrder());
System.out.println("ByteBuffer的字節順序為:" + buffer.order());
CharBuffer charBuffer = CharBuffer.allocate(10);
System.out.println("CharBuffer的字節順序為:" + charBuffer.order());
// 改動ButyBuffer的字節順序
buffer.order(ByteOrder.LITTLE_ENDIAN);
System.out.println("ByteBuffer的字節順序為:" + buffer.order());
/**
* 僅僅有ByteBuffer能夠創建直接緩沖區,用wrap函數創建的緩沖區都是非直接的緩沖區
*/
ByteBuffer redirectByteBuffer = ByteBuffer.allocateDirect(10);
System.out.println("推斷緩沖區是否為直接緩沖區:" + redirectByteBuffer.isDirect());
/**
* 先創建一個大端字節順序的ByteBuffer。然後再創建一個字符視圖緩沖區
*/
ByteBuffer bigByteBuffer = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
CharBuffer viewCharBuffer = bigByteBuffer.asCharBuffer();
viewCharBuffer.put("你好啊");
/**
* 在字符視圖的基礎上創建僅僅讀字符視圖,僅僅能讀而不能寫,否則拋出ReadOnlyBufferException
*/
CharBuffer onlyReadCharBuffer = viewCharBuffer.asReadOnlyBuffer();
viewCharBuffer.flip();
System.out.println("asCharBuffer()--->position=" + viewCharBuffer.position() + ",limit=" + viewCharBuffer.limit());
while (viewCharBuffer.hasRemaining()) {
System.out.println((char) viewCharBuffer.get());
}
/**
* 創建一個與原始緩沖區類似的新緩沖區,兩個緩沖區共享數據元素,擁有相同的容量,但每一個緩沖區擁有各自的位置、上界、標記屬性。 對一個緩沖區的數據元素所做的改變會反映在還有一個緩沖區上。新的緩沖區會繼承原始緩沖區的這些屬性。
*/
CharBuffer copyCharBuffer = viewCharBuffer.duplicate();
System.out.println("duplicate()--->position=" + copyCharBuffer.position() + ",limit=" + copyCharBuffer.limit());
copyCharBuffer.position(2);
while (copyCharBuffer.hasRemaining()) {
System.out.println((char) copyCharBuffer.get());
}
/**
* 創建原始緩沖區子集的新緩沖區,新緩沖區的內容將從該緩沖區的當前位置開始。對這個緩沖區的內容做出的改變將在反映在新的緩沖區上,可見,反之亦然;這兩個緩沖區的位置、限制和標記值將是獨立的。
*/
viewCharBuffer.position(1);
CharBuffer cutCharBuffer = viewCharBuffer.slice();
System.out.println("slice()--->position=" + cutCharBuffer.position() + ",limit=" + cutCharBuffer.limit()+",capacity="+cutCharBuffer.capacity());
while (cutCharBuffer.hasRemaining()) {
System.out.println((char) cutCharBuffer.get());
}
}
}
通俗編程——白話NIO之Buffer