NIO学习--缓冲区
Buffer其實(shí)就是是一個(gè)容器對(duì)象,它包含一些要寫入或者剛讀出的數(shù)據(jù)。在NIO中加入Buffer對(duì)象,體現(xiàn)了新庫與原I/O的一個(gè)重要區(qū)別。在面向流的I/O中,您將數(shù)據(jù)直接寫入或者將數(shù)據(jù)直接讀到Stream對(duì)象中。
在NIO庫中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時(shí),它是直接讀到緩沖區(qū)中的。在寫入數(shù)據(jù)時(shí),它是寫入到緩沖區(qū)中的。任何時(shí)候訪問NIO中的數(shù)據(jù),您都是將它放到緩沖區(qū)中。
緩沖區(qū)實(shí)質(zhì)上是一個(gè)數(shù)組。通常它是一個(gè)字節(jié)數(shù)組,但是也可以使用其他種類的數(shù)組。但是一個(gè)緩沖區(qū)不僅僅是一個(gè)數(shù)組。緩沖區(qū)提供了對(duì)數(shù)據(jù)的結(jié)構(gòu)化訪問,而且還可以跟蹤系統(tǒng)的讀/寫進(jìn)程。
最常用的緩沖區(qū)類型是ByteBuffer。 一個(gè)ByteBuffer可以在其底層字節(jié)數(shù)組上進(jìn)行g(shù)et/set操作(即字節(jié)的獲取和設(shè)置)。
?? ByteBuffer不是NIO中唯一的緩沖區(qū)類型。事實(shí)上,對(duì)于每一種基本Java類型都有一種緩沖區(qū)類型(只有boolean類型沒有其對(duì)應(yīng)的緩沖區(qū)類):
?? ByteBuffer
?? CharBuffer
?? ShortBuffer
?? IntBuffer
?? LongBuffer
?? FloatBuffer
?? DoubleBuffer
?每一個(gè)Buffer類都是Buffer接口的一個(gè)實(shí)例。 除了ByteBuffer, 每一個(gè)Buffer類都有完全一樣的操作,只是它們所處理的數(shù)據(jù)類型不一樣。因?yàn)榇蠖鄶?shù)標(biāo)準(zhǔn)I/O操作都使用ByteBuffer,所以它具有所有共享的 緩沖區(qū)操作以及一些特有的操作。我們來看一下Buffer的類層次圖吧:
每個(gè) Buffer 都有以下的屬性:
capacity
這個(gè) Buffer 最多能放多少數(shù)據(jù)。 capacity 一般在 buffer 被創(chuàng)建的時(shí)候指定。
limit
在 Buffer 上進(jìn)行的讀寫操作都不能越過這個(gè)下標(biāo)。當(dāng)寫數(shù)據(jù)到 buffer 中時(shí), limit 一般和 capacity 相等,當(dāng)讀數(shù)據(jù)時(shí), limit 代表 buffer 中有效數(shù)據(jù)的長度。
position
position變量跟蹤了向緩沖區(qū)中寫入了多少數(shù)據(jù)或者從緩沖區(qū)中讀取了多少數(shù)據(jù)。
更確切的說,當(dāng)您從通道中讀取數(shù)據(jù)到緩沖區(qū)中時(shí),它指示了下一個(gè)數(shù)據(jù)將放到數(shù)組的哪一個(gè)元素中。比如,如果您從通道中讀三個(gè)字節(jié)到緩沖區(qū)中,那么緩沖區(qū)的 position將會(huì)設(shè)置為3,指向數(shù)組中第4個(gè)元素。反之,當(dāng)您從緩沖區(qū)中獲取數(shù)據(jù)進(jìn)行寫通道時(shí),它指示了下一個(gè)數(shù)據(jù)來自數(shù)組的哪一個(gè)元素。比如,當(dāng)您 從緩沖區(qū)寫了5個(gè)字節(jié)到通道中,那么緩沖區(qū)的 position 將被設(shè)置為5,指向數(shù)組的第六個(gè)元素。
mark
一個(gè)臨時(shí)存放的位置下標(biāo)。調(diào)用 mark() 會(huì)將 mark 設(shè)為當(dāng)前的 position 的值,以后調(diào)用 reset() 會(huì)將 position 屬性設(shè)置為 mark 的值。 mark 的值總是小于等于 position 的值,如果將 position 的值設(shè)的比 mark 小,當(dāng)前的 mark 值會(huì)被拋棄掉。
這些屬性總是滿足以下條件:
0 <= mark <= position <= limit <= capacity
緩沖區(qū)的內(nèi)部實(shí)現(xiàn)機(jī)制:
下面我們就以數(shù)據(jù)從一個(gè)輸入通道拷貝到一個(gè)輸出通道為例,來詳細(xì)分析每一個(gè)變量,并說明它們是如何協(xié)同工作的:
?? 初始變量:
?? 我們首先觀察一個(gè)新創(chuàng)建的緩沖區(qū),以ByteBuffer為例,假設(shè)緩沖區(qū)的大小為8個(gè)字節(jié),ByteBuffer初始狀態(tài)如下:
?? 回想一下 ,limit決不能大于capacity,此例中這兩個(gè)值都被設(shè)置為8。我們通過將它們指向數(shù)組的尾部之后(第8個(gè)槽位)來說明這點(diǎn)。
?? 我們?cè)賹osition設(shè)置為0。表示如果我們讀一些數(shù)據(jù)到緩沖區(qū)中,那么下一個(gè)讀取的數(shù)據(jù)就進(jìn)入 slot 0。如果我們從緩沖區(qū)寫一些數(shù)據(jù),從緩沖區(qū)讀取的下一個(gè)字節(jié)就來自slot 0。position設(shè)置如下所示:
?? 由于緩沖區(qū)的最大數(shù)據(jù)容量capacity不會(huì)改變,所以我們?cè)谙旅娴挠懻撝锌梢院雎运?br />
?? 第一次讀取:
?? 現(xiàn)在我們可以開始在新創(chuàng)建的緩沖區(qū)上進(jìn)行讀/寫操作了。首先從輸入通道中讀一些數(shù)據(jù)到緩沖區(qū)中。第一次讀取得到三個(gè)字節(jié)。它們被放到數(shù)組中從 position開始的位置,這時(shí)position被設(shè)置為0。讀完之后,position就增加到了3,如下所示,limit沒有改變。
?? 第二次讀取:
?? 在第二次讀取時(shí),我們從輸入通道讀取另外兩個(gè)字節(jié)到緩沖區(qū)中。這兩個(gè)字節(jié)儲(chǔ)存在由position所指定的位置上, position因而增加2,limit沒有改變。
?? flip:
?? 現(xiàn)在我們要將數(shù)據(jù)寫到輸出通道中。在這之前,我們必須調(diào)用flip()方法。 其源代碼如下:
????public?final?Buffer?flip()?{??limit?=?position;??position?=?0;??mark?=?-1;??return?this;??}
?? 這個(gè)方法做兩件非常重要的事:
?? i? 它將limit設(shè)置為當(dāng)前position。
?? ii 它將position設(shè)置為0。
?? 上一個(gè)圖顯示了在flip之前緩沖區(qū)的情況。下面是在flip之后的緩沖區(qū):
?? 我們現(xiàn)在可以將數(shù)據(jù)從緩沖區(qū)寫入通道了。position被設(shè)置為0,這意味著我們得到的下一個(gè)字節(jié)是第一個(gè)字節(jié)。limit已被設(shè)置為原來的position,這意味著它包括以前讀到的所有字節(jié),并且一個(gè)字節(jié)也不多。
?? 第一次寫入:
?? 在第一次寫入時(shí),我們從緩沖區(qū)中取四個(gè)字節(jié)并將它們 寫入輸出通道。這使得position增加到4,而limit不變,如下所示:
?? 第二次寫入:
?? 我們只剩下一個(gè)字節(jié)可寫了。limit在我們調(diào)用flip()時(shí)被設(shè)置為5,并且position不能超過limit。 所以最后一次寫入操作從緩沖區(qū)取出一個(gè)字節(jié)并將它寫入輸出通道。這使得position增加到5,并保持limit不變,如下所示:
?? clear:
?? 最后一步是調(diào)用緩沖區(qū)的clear()方法。這個(gè)方法重設(shè)緩沖區(qū)以便接收更多的字節(jié)。其源代碼如下:
????public?final?Buffer?clear()?{??osition?=?0;??limit?=?capacity;??mark?=?-1;??return?this;??}
?? clear做兩種非常重要的事情:
?? i 它將limit設(shè)置為與capacity相同。
?? ii 它設(shè)置position為0。
?? 下圖顯示了在調(diào)用clear()后緩沖區(qū)的狀態(tài), 此時(shí)緩沖區(qū)現(xiàn)在可以接收新的數(shù)據(jù)了。
至此,我們只是使用緩沖區(qū)將數(shù)據(jù)從一個(gè)通道轉(zhuǎn)移到另一個(gè)通道,然而,程序經(jīng)常需要直接處理數(shù)據(jù)。例如,您可能需要將用戶數(shù)據(jù)保存到磁盤。在這種情況下,您必須將這些數(shù)據(jù)直接放入緩沖區(qū),然后用通道將緩沖區(qū)寫入磁盤。 或者,您可能想要從磁盤讀取用戶數(shù)據(jù)。在這種情況下,您要將數(shù)據(jù)從通道讀到緩沖區(qū)中,然后檢查緩沖區(qū)中的數(shù)據(jù)。實(shí)際上,每一個(gè)基本類型的緩沖區(qū)都為我們提供了直接訪問緩沖區(qū)中數(shù)據(jù)的方法,我們以ByteBuffer為例,分析如何使用其提供的get()和put()方法直接訪問緩沖區(qū)中的數(shù)據(jù)。
?? a)??? get()
?? ByteBuffer類中有四個(gè)get()方法:
第一個(gè)方法獲取單個(gè)字節(jié)。第二和第三個(gè)方法將一組字節(jié)讀到一個(gè)數(shù)組中。第四個(gè)方法從緩沖區(qū)中的特定位置獲取字節(jié)。那些返回ByteBuffer的方法只是返回調(diào)用它們的緩沖區(qū)的this值。 此外,我們認(rèn)為前三個(gè)get()方法是相對(duì)的,而最后一個(gè)方法是絕對(duì)的?!跋鄬?duì)”意味著get()操作服從limit和position值,更明確地說, 字節(jié)是從當(dāng)前position讀取的,而position在get之后會(huì)增加。另一方面,一個(gè)“絕對(duì)”方法會(huì)忽略limit和position值,也不會(huì) 影響它們。事實(shí)上,它完全繞過了緩沖區(qū)的統(tǒng)計(jì)方法。 上面列出的方法對(duì)應(yīng)于ByteBuffer類。其他類有等價(jià)的get()方法,這些方法除了不是處理字節(jié)外,其它方面是是完全一樣的,它們處理的是與該緩沖區(qū)類相適應(yīng)的類型。
注:這里我們著重看一下第二和第三這兩個(gè)方法
ByteBuffer?get(?byte?dst[]?);?? ByteBuffer?get(?byte?dst[],?int?offset,?int?length?);這兩個(gè)get()主要用來進(jìn)行批量的移動(dòng)數(shù)據(jù),可供從緩沖區(qū)到數(shù)組進(jìn)行的數(shù)據(jù)復(fù)制使用。第一種形式只將一個(gè)數(shù)組 作為參數(shù),將一個(gè)緩沖區(qū)釋放到給定的數(shù)組。第二種形式使用 offset 和 length 參數(shù)來指 定目標(biāo)數(shù)組的子區(qū)間。這些批量移動(dòng)的合成效果與前文所討論的循環(huán)是相同的,但是這些方法 可能高效得多,因?yàn)檫@種緩沖區(qū)實(shí)現(xiàn)能夠利用本地代碼或其他的優(yōu)化來移動(dòng)數(shù)據(jù)。
buffer.get(myArray)??? 等價(jià)于:
buffer.get(myArray,0,myArray.length);
注:如果您所要求的數(shù)量的數(shù)據(jù)不能被傳送,那么不會(huì)有數(shù)據(jù)被傳遞,緩沖區(qū)的狀態(tài)保持不 變,同時(shí)拋出 BufferUnderflowException 異常。因此當(dāng)您傳入一個(gè)數(shù)組并且沒有指定長度,您就相當(dāng)于要求整個(gè)數(shù)組被填充。如果緩沖區(qū)中的數(shù)據(jù)不夠完全填滿數(shù)組,您會(huì)得到一個(gè) 異常。這意味著如果您想將一個(gè)小型緩沖區(qū)傳入一個(gè)大數(shù)組,您需要明確地指定緩沖區(qū)中剩 余的數(shù)據(jù)長度。上面的第一個(gè)例子不會(huì)如您第一眼所推出的結(jié)論那樣,將緩沖區(qū)內(nèi)剩余的數(shù)據(jù) 元素復(fù)制到數(shù)組的底部。例如下面的代碼:
????????String?str?=?"com.xiaoluo.nio.MultipartTransfer";ByteBuffer?buffer?=?ByteBuffer.allocate(50);????????for(int?i?=?0;?i?<?str.length();?i++){buffer.put(str.getBytes()[i]);}buffer.flip();byte[]?buffer2?=?new?byte[100];buffer.get(buffer2);buffer.get(buffer2,?0,?length);System.out.println(new?String(buffer2));
這里就會(huì)拋出java.nio.BufferUnderflowException異常,因?yàn)閿?shù)組希望緩存區(qū)的數(shù)據(jù)能將其填滿,如果填不滿,就會(huì)拋出異常,所以代碼應(yīng)該改成下面這樣:
????????//得到緩沖區(qū)未讀數(shù)據(jù)的長度length?=[]?buffer2?=??[1000,?length);
b)??? put()
?? ByteBuffer類中有五個(gè)put()方法:
第一個(gè)方法 寫入(put)單個(gè)字節(jié)。第二和第三個(gè)方法寫入來自一個(gè)數(shù)組的一組字節(jié)。第四個(gè)方法將數(shù)據(jù)從一個(gè)給定的源ByteBuffer寫入這個(gè) ByteBuffer。第五個(gè)方法將字節(jié)寫入緩沖區(qū)中特定的 位置 。那些返回ByteBuffer的方法只是返回調(diào)用它們的緩沖區(qū)的this值。 與get()方法一樣,我們將把put()方法劃分為“相對(duì)”或者“絕對(duì)”的。前四個(gè)方法是相對(duì)的,而第五個(gè)方法是絕對(duì)的。上面顯示的方法對(duì)應(yīng)于ByteBuffer類。其他類有等價(jià)的put()方法,這些方法除了不是處理字節(jié)之外,其它方面是完全一樣的。它們處理的是與該緩沖區(qū)類相適應(yīng)的類型。
c)??? 類型化的 get() 和 put() 方法
?? 除了前些小節(jié)中描述的get()和put()方法, ByteBuffer還有用于讀寫不同類型的值的其他方法,如下所示:
?? getByte()
?? getChar()
?? getShort()
?? getInt()
?? getLong()
?? getFloat()
?? getDouble()
?? putByte()
?? putChar()
?? putShort()
?? putInt()
?? putLong()
?? putFloat()
?? putDouble()
?? 事實(shí)上,這其中的每個(gè)方法都有兩種類型:一種是相對(duì)的,另一種是絕對(duì)的。它們對(duì)于讀取格式化的二進(jìn)制數(shù)據(jù)(如圖像文件的頭部)很有用。
?? 下面的內(nèi)部循環(huán)概括了使用緩沖區(qū)將數(shù)據(jù)從輸入通道拷貝到輸出通道的過程。
????????while(true){????????????//clear方法重設(shè)緩沖區(qū),可以讀新內(nèi)容到buffer里????????????buffer.clear();????????????int?val?=?inChannel.read(buffer);????????????if(val?==?-1){????????????????break;}????????????//flip方法讓緩沖區(qū)的數(shù)據(jù)輸出到新的通道里面????????????buffer.flip();outChannel.write(buffer);}
?? read()和write()調(diào)用得到了極大的簡化,因?yàn)樵S多工作細(xì)節(jié)都由緩沖區(qū)完成了。clear()和flip()方法用于讓緩沖區(qū)在讀和寫之間切換。
好了,緩沖區(qū)的內(nèi)容就暫且寫到這里,下一篇我們將繼續(xù)NIO的學(xué)習(xí)--通道(Channel).
?
轉(zhuǎn)載于:https://blog.51cto.com/3237526/1606323
總結(jié)
以上是生活随笔為你收集整理的NIO学习--缓冲区的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: intelssd在linux固件升级,B
- 下一篇: IEEE MAC地址分配