緩沖區本質上是一塊可以寫入數據,然后可以從中讀取數據的內存,這塊內存中有很多可以存儲byte(或int、char等)的小單元。這塊內存被包裝成NIO Buffer對象,並提供了一組方法,用來方便的訪問該塊內存。
為了理解Buffer的工作原理,需要熟悉它的三個屬性:
- capacity
- position
- limit
簡單的解釋這三個屬性的含義可以概括為:capacity代表這塊Buffer的容量,position代表下一次讀(或寫)的位置,limit代表本次讀(或寫)的極限位置。這么簡單說一下你當然是聽不懂的啦(這樣一下你就聽懂了,豈不是顯得我很沒有存在感)所以下面開始詳細的講解。
capacity
作為一個內存塊,Buffer有一個固定的大小值(這個大小是剛開始申請的),叫作“capacity”.你只能往里寫capacity個byte、int,char等類型。
position
當你要寫數據到Buffer中時,position表示當前可寫的位置。初始的position值為0(也可以用過方法進行改變)。當一個byte、int等數據寫到Buffer后, position會向前移動到下一個可插入數據的Buffer單元。position最大可為capacity – 1.
當讀取數據時,也是從某個特定位置讀。當從Buffer的position處讀取數據完成時,position向前移動到下一個可讀的位置。
limit
在寫數據時,Buffer的limit表示你最多能往Buffer里寫多少數據,position移動到limit寫操作停止。初始limit的值等於Buffer的capacity。當讀取數據時, limit表示你最多能讀到多少數據,position移動到limit讀操作停止。
無論在讀數據時還是在寫數據時,只要position超過了limit就會拋出異常。
控制position和limit的值
capacity的值是根據申請Buffer的大小和種類確定的,所以不能改變。而position和limit就可以根據我的需要而改變了,首先介紹一下如何查看這三個屬性的值:buffer.limit()
、buffer.position()
、buffer.capacity()
這三個方法直觀、方便,我們就不再羅嗦。
接下來我們要着重看一下buffer.flip()
這個方法一般用在寫到讀切換的時候。這個方法的能力就是將limit設為position的值,再是將position設為0。
這么做的用處是什么呢?你想想,在寫數據的時候從Buffer的開始處—0位置到position位置之間已經寫滿了數據,如果這時候我們想要從頭開始讀數據的話,就要將position指向0,以便可以讀取0位置的數據,然后逐個向下讀取;但要讀到什么位置為止呢?如果整個Buffer都讀完的話,剛才所寫的最后一個單元以后的單元,都是空,讀取它們沒有意義。所以讀取到剛才所寫的最后一個單元,是明智之舉。而在將position置為0之前,position值就是剛才所寫的最后一個單元的位置。所以在寫到讀切換的時候,將limit設為position的值,再是將position設為0。
這個明白了以后一切都順了。buffer.clear()
是清空Buffer的方法,但它沒有真正的清除,只是將position置為0,將limit置為capacity;這樣一來,你的寫操作就可以將原來的數據覆蓋了。就是這么簡單。buffer.rewind()
是將position置為0,這樣一來就可以將buffer再重新讀一遍,當然你還可通過它干很多事。
其實還有很多有用、有趣的方法,看看api文檔吧。
基礎實例 和 btye與其他類型的轉換
說了這么多理論,再不上代碼就有人得罵街了,來個最基礎的申請buffer和基本的讀寫吧
ByteBuffer bb = ByteBuffer.allocate(48);
/*向ByteBuffer中put數據的時候,一下四種形式都可以
* put(byte b)
* put(byte[] src)
* put(byte[] src, int offset, int length)
* put(ByteBuffer src)
* 四種形式都會移動position指針
*/
bb.put(new byte[]{1,2,4,2,-13});
bb.flip();
//hasRemaining()的作用是看看position到沒到limit位置
while(bb.hasRemaining()) {
System.out.println(bb.get());
}
還得來一段理論,再聽我扯一會。Buffer共有類型有以下幾種
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer(這哥們兒有點特殊,以后單獨再講)
Buffer在nio中的主要作用就是與channel交互。但這幾種類型中能與channel交互的只有ByteBuffer(坑爹的吧!)所以在用其他類型Buffer的時候,一般都是先將ByteBuffer轉化為想用的類型,用的是byteBuffer.asCharBuffer()
、byteBuffer.asIntBuffer()
等方法進行轉換。這種方式用術語來講,叫做“產生其他種類Buffer的視圖”;意思就是底層是ByteBuffer,但看起來是其它種類的Buffer,可以用相應的方法,但是視圖發生了讀寫,底層的ByteBuffer也會發生變化。
下面這段是將ByteBuffer轉化為CharBuffer視圖的例子
ByteBuffer bb = ByteBuffer.allocate(1024);
//將ByteBuffer轉化為CharBuffer視圖后,再調用put,ByteBuffer中的position指針不會移動
bb.asCharBuffer().put("Hello World");
//為了能正確的輸出,這里改變了limit指針的位置,使之變到了字符數組的末尾
bb.limit("Hello World".length()*Character.BYTES);//字符數組長度*每個字符占的字節數
while(bb.hasRemaining()) {
System.out.print(bb.getChar());
}
/*也可用如下的方法輸出
* while((c=bb.getChar())!=0) {
* System.out.print(c);
* }
*/
這段例子告訴我們,視圖發生了讀寫,底層的ByteBuffer是有感知的,以及感知如何展現出來。但真正用的時候,沒這么蠻煩。因為學了后面的channel,就知道了,channel直接就把整個ByteBuffer都拿走了,就不用這樣一個個的輸出了。而且一個個輸出的話也可以直接利用視圖層,向下看
ByteBuffer bb = ByteBuffer.allocate(1024);
IntBuffer ib = bb.asIntBuffer();
ib.put(new int[]{1,42,12,-12});
/*將ByteBuffer轉化為IntBuffer視圖后,再調用put,ByteBuffer中的position指針不會移動
* 但是所生成的IntBuffer中的position會按正常方式移動
* 而且整個IntBuffer的capacity會按照byte 和 int 之間的所占字節大小比例而改變*/
System.out.println("ByteBuffer.position = "+bb.position());
System.out.println("ByteBuffer.limit = "+bb.limit());
System.out.println("ByteBuffer.capacity = "+bb.capacity());
System.out.println("IntBuffer.position = "+ib.position());
System.out.println("IntBuffer.limit = "+ib.limit());
System.out.println("IntBuffer.capacity = "+ib.capacity());
ib.flip();
while(ib.hasRemaining()) {
System.out.println(ib.get());
}
會了這些Buffer的知識就差不多了,就到這里了。多打打例子代碼、多體會體會,就可以洗洗睡了,拜拜
還是那句話,有問題及時告訴我