Java NIO中的Buffer用于和NIO通道進(jìn)行交互。數(shù)據(jù)是從通道讀入緩沖區(qū)拦盹,從緩沖區(qū)寫入到通道中的鹃祖。緩沖區(qū)本質(zhì)上是一塊固定大小的內(nèi)存,其作用是一個(gè)存儲器或運(yùn)輸器普舆。這塊內(nèi)存被包裝成NIO Buffer對象恬口,并提供了一組方法,用來方便的訪問該塊內(nèi)存沼侣。
1祖能、緩沖區(qū)基礎(chǔ)
理論上,Buffer是經(jīng)過包裝后的數(shù)組华临,而Buffer提供了一組通用api對這個(gè)數(shù)組進(jìn)行操作芯杀。
1.1、屬性:
容量(Capacity)
緩沖區(qū)能夠容納的數(shù)據(jù)元素的最大數(shù)量。這一容量在緩沖區(qū)創(chuàng)建時(shí)被設(shè)定揭厚,并且永遠(yuǎn)不能被改變却特。
上界(Limit)
緩沖區(qū)的第一個(gè)不能被讀或?qū)懙脑亍筛圆;蛘哒f裂明,緩沖區(qū)中現(xiàn)存元素的計(jì)數(shù)。
位置(Position)
下一個(gè)要被讀或?qū)懙脑氐乃饕N恢脮詣佑上鄳?yīng)的get( )和put( )函數(shù)更新闽晦。
標(biāo)記(Mark)
一個(gè)備忘位置。調(diào)用mark( )來設(shè)定mark = postion提岔。調(diào)用reset( )設(shè)定position = mark仙蛉。標(biāo)記在設(shè)定前是未定義的(undefined)。這四個(gè)屬性之間總是遵循以下關(guān)系: 0 <= mark <= position <= limit <= capacity 讓我們來看看這些屬性在實(shí)際應(yīng)用中的一些例子碱蒙。下圖展示了一個(gè)新創(chuàng)建的容量為10的ByteBuffer邏輯視圖荠瘪。
位置被設(shè)置為0,而且容量和上界被設(shè)置為9赛惩,超過緩沖區(qū)能容納的最后一個(gè)字節(jié)哀墓,標(biāo)記初始未定義(-1),當(dāng)緩沖區(qū)創(chuàng)建完畢喷兼,則容量就不變篮绰,而其他屬性隨讀寫等操作改變。
1.2季惯、緩存區(qū)API
public final int capacity( ) :返回緩沖區(qū)的容量
public final int position( ) :返回緩沖區(qū)的位置
public final Buffer position (int newPosition):設(shè)置緩沖區(qū)的位置
public final int limit( ) :返回緩沖區(qū)的上界
public final Buffer limit (int newLimit) :設(shè)置緩沖區(qū)的上界
public final Buffer mark( ) :設(shè)置緩沖區(qū)的標(biāo)記
public final Buffer reset( ) :將緩沖區(qū)的位置重置為標(biāo)記的位置
public final Buffer clear( ) :清除緩沖區(qū)
public final Buffer flip( ) :反轉(zhuǎn)緩沖區(qū)
public final Buffer rewind( ) :重繞緩沖區(qū)
public final int remaining( ) :返回當(dāng)為位置與上界之間的元素?cái)?shù)
public final boolean hasRemaining( ) :緩存的位置與上界之間是否還有元素
public abstract boolean isReadOnly( ):緩沖區(qū)是否為只讀緩沖區(qū)
1.3吠各、存取
Buffer提供了一組獲取緩沖區(qū)的api:
public abstract byte get( ):獲取緩沖區(qū)當(dāng)前位置上的字節(jié),然后增加位置星瘾;
public abstract byte get (int index):獲取指定索引中的字節(jié)走孽;
public abstract ByteBuffer put (byte b):將字節(jié)寫入當(dāng)前位置的緩沖區(qū)中,并增加位置琳状;
public abstract ByteBuffer put (int index, byte b):將自己寫入給定位置的緩沖區(qū)中磕瓷;
Get和put可以是相對的或者是絕對的。在前面的程序列表中念逞,相對方案是不帶有索引參數(shù)的函數(shù)困食。當(dāng)相對函數(shù)被調(diào)用時(shí),位置在返回時(shí)前進(jìn)一翎承。如果位置前進(jìn)過多硕盹,相對運(yùn)算就會拋出異常。對于put()叨咖,如果運(yùn)算會導(dǎo)致位置超出上界瘩例,就會拋出BufferOverflowException異常啊胶。對于get(),如果位置不小于上界垛贤,就會拋出BufferUnderflowException異常焰坪。絕對存取不會影響緩沖區(qū)的位置屬性,但是如果您所提供的索引超出范圍(負(fù)數(shù)或不小于上界)聘惦,也將拋出IndexOutOfBoundsException異常某饰。
1.4、翻轉(zhuǎn)
我們已經(jīng)寫滿了緩沖區(qū)善绎,現(xiàn)在我們必須準(zhǔn)備將其清空黔漂。我們想把這個(gè)緩沖區(qū)傳遞給一個(gè)通道,以使內(nèi)容能被全部寫出禀酱。但如果通道現(xiàn)在在緩沖區(qū)上執(zhí)行g(shù)et()炬守,那么它將從我們剛剛插入的有用數(shù)據(jù)之外取出未定義數(shù)據(jù)。如果我們將位置值重新設(shè)為0比勉,通道就會從正確位置開始獲取劳较,但是它是怎樣知道何時(shí)到達(dá)我們所插入數(shù)據(jù)末端的呢驹止?這就是上界屬性被引入的目的浩聋。上界屬性指明了緩沖區(qū)有效內(nèi)容的末端。我們需要將上界屬性設(shè)置為當(dāng)前位置臊恋,然后將位置重置為0衣洁。
我們可以人工用下面的代碼實(shí)現(xiàn): buffer.limit(buffer.position()).position(0);
但這種從填充到釋放狀態(tài)的緩沖區(qū)翻轉(zhuǎn)是API設(shè)計(jì)者預(yù)先設(shè)計(jì)好的,他們?yōu)槲覀兲峁┝艘粋€(gè)非常便利的函數(shù): Buffer.flip();
Flip()函數(shù)將一個(gè)能夠繼續(xù)添加數(shù)據(jù)元素的填充狀態(tài)的緩沖區(qū)翻轉(zhuǎn)成一個(gè)準(zhǔn)備讀出元素的釋放狀態(tài)抖仅。
翻轉(zhuǎn)前:
翻轉(zhuǎn)后:
flip()源碼如下:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
Rewind()函數(shù)與flip()相似坊夫,但不影響上界屬性。它只是將位置值設(shè)回0撤卢。您可以使用rewind()后退环凿,重讀已經(jīng)被翻轉(zhuǎn)的緩沖區(qū)中的數(shù)據(jù)。
rewind()源碼如下:
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
1.5放吩、釋放
如果您接收到一個(gè)在別處被填滿的緩沖區(qū)智听,您可能需要在檢索內(nèi)容之前將其翻轉(zhuǎn)。例如渡紫,如果一個(gè)通道的read()操作完成到推,而您想要查看被通道放入緩沖區(qū)內(nèi)的數(shù)據(jù),那么您需要在調(diào)用get()之前翻轉(zhuǎn)緩沖區(qū)惕澎。通道對象在緩沖區(qū)上調(diào)用put()增加數(shù)據(jù)莉测;put和read可以隨意混合使用。
布爾函數(shù)hasRemaining()會在釋放緩沖區(qū)時(shí)告訴您是否已經(jīng)達(dá)到緩沖區(qū)的上界唧喉。以下是一種將數(shù)據(jù)元素從緩沖區(qū)釋放到一個(gè)數(shù)組的方法捣卤。
for (int i = 0; buffer.hasRemaining( ), i++) {
myByteArray [i] = buffer.get( );
}
作為選擇忍抽,remaining()函數(shù)將告知您從當(dāng)前位置到上界還剩余的元素?cái)?shù)目。如果您對緩沖區(qū)有專門的控制董朝,這種方法會更高效梯找,因?yàn)樯辖绮粫诿看窝h(huán)重復(fù)時(shí)都被檢查。
緩沖區(qū)并不是多線程安全的益涧。如果您想以多線程同時(shí)存取特定的緩沖區(qū)锈锤,您需要在存取緩沖區(qū)之前進(jìn)行同步。
一旦緩沖區(qū)對象完成填充并釋放闲询,它就可以被重新使用了久免。Clear()函數(shù)將緩沖區(qū)重置為空狀態(tài)。它并不改變緩沖區(qū)中的任何數(shù)據(jù)元素扭弧,而是僅僅將上界設(shè)為容量的值阎姥,并把位置設(shè)回0。
1.6 鸽捻、壓縮
有時(shí)呼巴,您可能只想從緩沖區(qū)中釋放一部分?jǐn)?shù)據(jù),而不是全部御蒲,然后重新填充衣赶。為了實(shí)現(xiàn)這一點(diǎn),未讀的數(shù)據(jù)元素需要下移以使第一個(gè)元素索引為0厚满。盡管重復(fù)這樣做會效率低下府瞄,但這有時(shí)非常必要,而API對此為您提供了一個(gè)compact()函數(shù)碘箍。這一緩沖區(qū)工具在復(fù)制數(shù)據(jù)時(shí)要比您使用get()和put()函數(shù)高效得多遵馆。所以當(dāng)您需要時(shí),請使用compact()丰榴。
compact()之前的緩沖區(qū):
compact()之后的緩沖區(qū):
- 這里發(fā)生了幾件事货邓。您會看到數(shù)據(jù)元素2-5被復(fù)制到0-3位置。位置4和5不受影響四濒,但現(xiàn)在正在或已經(jīng)超出了當(dāng)前位置换况,因此是“死的”。它們可以被之后的put()調(diào)用重寫峻黍。
- 位置已經(jīng)被設(shè)為被復(fù)制的數(shù)據(jù)元素的數(shù)目复隆。也就是說,緩沖區(qū)現(xiàn)在被定位在緩沖區(qū)中最后一個(gè)“存活”元素后插入數(shù)據(jù)的位置姆涩。
- 上界屬性被設(shè)置為容量的值挽拂,因此緩沖區(qū)可以被再次填滿。
- 調(diào)用compact()的作用是丟棄已經(jīng)釋放的數(shù)據(jù)骨饿,保留未釋放的數(shù)據(jù)亏栈,并使緩沖區(qū)對重新填充容量準(zhǔn)備就緒台腥。
您可以用這種 類似于先入先出(FIFO)隊(duì)列的方式使用緩沖區(qū)。當(dāng)然也存在更高效的算法(緩沖區(qū)移位并不是一個(gè)處理隊(duì)列的非常高效的方法)绒北。但是壓縮對于使緩沖區(qū)與您從端口中讀入的數(shù)據(jù)(包)邏輯塊流的同步來說也許是一種便利的方法黎侈。
1.7、標(biāo)記
標(biāo)記闷游,使緩沖區(qū)能夠記住一個(gè)位置并在之后將其返回峻汉。緩沖區(qū)的標(biāo)記在mark( )函數(shù)被調(diào)用之前是未定義的,調(diào)用時(shí)標(biāo)記被設(shè)為當(dāng)前位置的值脐往。reset( )函數(shù)將位置設(shè)為當(dāng)前的標(biāo)記值休吠。如果標(biāo)記值未定義,調(diào)用reset( )將導(dǎo)致InvalidMarkException異常业簿。一些緩沖區(qū)函數(shù)會拋棄已經(jīng)設(shè)定的標(biāo)記(rewind( )瘤礁,clear( ),以及flip( )總是拋棄標(biāo)記)梅尤。如果新設(shè)定的值比當(dāng)前的標(biāo)記小柜思,調(diào)用limit( )或position( )帶有索引參數(shù)的版本會拋棄標(biāo)記。
1.8巷燥、比較
有時(shí)候比較兩個(gè)緩沖區(qū)所包含的數(shù)據(jù)是很有必要的赡盘。所有的緩沖區(qū)都提供了一個(gè)常規(guī)的equals( )函數(shù)用以測試兩個(gè)緩沖區(qū)的是否相等,以及一個(gè)compareTo( )函數(shù)用以比較緩沖區(qū)亡脑。
public boolean equals (Object ob)
public int compareTo (Object ob)
如果每個(gè)緩沖區(qū)中剩余的內(nèi)容相同邀跃,那么equals( )函數(shù)將返回true,否則返回false拍屑。因?yàn)檫@個(gè)測試是用于嚴(yán)格的相等而且是可換向的。前面的程序清單中的緩沖區(qū)名稱可以顛倒坑傅,并會產(chǎn)生相同的結(jié)果僵驰。
兩個(gè)緩沖區(qū)被認(rèn)為相等的充要條件是:
- 兩個(gè)對象類型相同唁毒。包含不同數(shù)據(jù)類型的buffer永遠(yuǎn)不會相等,而且buffer絕不會等于非buffer對象浆西。
- 兩個(gè)對象都剩余同樣數(shù)量的元素粉私。Buffer的容量不需要相同,而且緩沖區(qū)中剩余數(shù)據(jù)的索引也不必相同近零。但每個(gè)緩沖區(qū)中剩余元素的數(shù)目(從位置到上界)必須相同诺核。
- 在每個(gè)緩沖區(qū)中應(yīng)被Get()函數(shù)返回的剩余數(shù)據(jù)元素序列必須一致。
如果不滿足以上任意條件漓摩,就會返回false。
相等的緩沖區(qū):
不等的緩沖區(qū):
緩沖區(qū)也支持用compareTo( )函數(shù)以詞典順序進(jìn)行比較管毙。這一函數(shù)在緩沖區(qū)參數(shù)小于桌硫,等于,或者大于引用compareTo( )的對象實(shí)例時(shí)鞍泉,分別返回一個(gè)負(fù)整數(shù),0和正整數(shù)边器。這些就是所有典型的緩沖區(qū)所實(shí)現(xiàn)的java.lang.Comparable接口語義托修。這意味著緩沖區(qū)數(shù)組可以通過調(diào)用java.util.Arrays.sort()函數(shù)按照它們的內(nèi)容進(jìn)行排序。
與equals( )相似睦刃,compareTo( )不允許不同對象間進(jìn)行比較。但compareTo( )更為嚴(yán)格:如果您傳遞一個(gè)類型錯(cuò)誤的對象际长,它會拋出ClassCastException異常兴泥,但equals( )只會返回false。
比較是針對每個(gè)緩沖區(qū)內(nèi)剩余數(shù)據(jù)進(jìn)行的搓彻,與它們在equals( )中的方式相同,直到不相等的元素被發(fā)現(xiàn)或者到達(dá)緩沖區(qū)的上界怔接。如果一個(gè)緩沖區(qū)在不相等元素發(fā)現(xiàn)前已經(jīng)被耗盡,較短的緩沖區(qū)被認(rèn)為是小于較長的緩沖區(qū)稀轨。不像equals( ),compareTo( )不可交換:順序問題谎势。
1.9、批量移動
buffer API提供了向緩沖區(qū)內(nèi)外批量移動數(shù)據(jù)元素的函數(shù):
public CharBuffer get (char [] dst) :
public CharBuffer get (char [] dst, int offset, int length)
public final CharBuffer put (char脏榆、[] src)
public CharBuffer put (char [] src, int offset, int length)
public CharBuffer put (CharBuffer src)
public final CharBuffer put (String src)
public CharBuffer put (String src, int start, int end)
有兩種形式的get( )可供從緩沖區(qū)到數(shù)組進(jìn)行的數(shù)據(jù)復(fù)制使用。第一種形式只將一個(gè)數(shù)組作為參數(shù)吁断,將一個(gè)緩沖區(qū)釋放到給定的數(shù)組坞生。第二種形式使用offset和length參數(shù)來指定目標(biāo)數(shù)組的子區(qū)間。這些批量移動的合成效果與前文所討論的循環(huán)是相同的是己,但是這些方法可能高效得多,因?yàn)檫@種緩沖區(qū)實(shí)現(xiàn)能夠利用本地代碼或其他的優(yōu)化來移動數(shù)據(jù)沛厨。
如果您所要求的數(shù)量的數(shù)據(jù)不能被傳送摔认,那么不會有數(shù)據(jù)被傳遞,緩沖區(qū)的狀態(tài)保持不變参袱,同時(shí)拋出BufferUnderflowException異常。因此當(dāng)您傳入一個(gè)數(shù)組并且沒有指定長度剿牺,您就相當(dāng)于要求整個(gè)數(shù)組被填充况鸣。如果緩沖區(qū)中的數(shù)據(jù)不夠完全填滿數(shù)組,您會得到一個(gè)異常镐捧。這意味著如果您想將一個(gè)小型緩沖區(qū)傳入一個(gè)大型數(shù)組臭增,您需要明確地指定緩沖區(qū)中剩余的數(shù)據(jù)長度。
對于以下幾種情況的數(shù)據(jù)復(fù)制會發(fā)生異常:
- 如果你所要求的數(shù)量的數(shù)據(jù)不能被傳送列牺,那么不會有數(shù)據(jù)被傳遞拗窃,緩沖區(qū)的狀態(tài)保持不變泌辫,同時(shí)拋出BufferUnderflowException異常九默。
- 如果緩沖區(qū)中的數(shù)據(jù)不夠完全填滿數(shù)組驼修,你會得到一個(gè)異常墨礁。這意味著如果你想將一個(gè)小型緩沖區(qū)傳入一個(gè)大型數(shù)組恩静,你需要明確地指定緩沖區(qū)中剩余的數(shù)據(jù)長度驶乾。
2轻掩、創(chuàng)建緩沖區(qū)
public static CharBuffer allocate (int capacity):創(chuàng)建容量為capacity的緩沖區(qū)
public static CharBuffer wrap (char [] array) :將array數(shù)組包裝成緩沖區(qū)
public static CharBuffer wrap (char [] array, int offset, int length) :將array包裝為緩沖區(qū)唇牧,position=offset丐重,limit=lenth
public final boolean hasArray( ) :判斷緩沖區(qū)底層實(shí)現(xiàn)是否為數(shù)組
public final char [] array( ) :返回緩沖中的數(shù)組
public final int arrayOffset( ) :返回緩沖區(qū)數(shù)據(jù)在數(shù)組中存儲的開始位置的偏移量杆查;
新的緩沖區(qū)是由分配或包裝操作創(chuàng)建的崖蜜。分配操作創(chuàng)建一個(gè)緩沖區(qū)對象并分配一個(gè)私有的空間來儲存容量大小的數(shù)據(jù)元素豫领。包裝操作創(chuàng)建一個(gè)緩沖區(qū)對象但是不分配任何空間來儲存數(shù)據(jù)元素等恐。它使用您所提供的數(shù)組作為存儲空間來儲存緩沖區(qū)中的數(shù)據(jù)元素囱稽。
要分配一個(gè)容量為100個(gè)char變量的Charbuffer:
CharBuffer charBuffer = CharBuffer.allocate (100);
這段代碼隱含地從堆空間中分配了一個(gè)char型數(shù)組作為備份存儲器來儲存100個(gè)char變量战惊。
如果您想提供您自己的數(shù)組用做緩沖區(qū)的備份存儲器样傍,請調(diào)用wrap()函數(shù):
char [] myArray = new char [100]; CharBuffer charbuffer = CharBuffer.wrap (myArray);
這段代碼構(gòu)造了一個(gè)新的緩沖區(qū)對象衫哥,但數(shù)據(jù)元素會存在于數(shù)組中撤逢。這意味著通過調(diào)用put()函數(shù)造成的對緩沖區(qū)的改動會直接影響這個(gè)數(shù)組,而且對這個(gè)數(shù)組的任何改動也會對這個(gè)緩沖區(qū)對象可見互例。帶有offset和length作為參數(shù)的wrap()函數(shù)版本則會 構(gòu)造一個(gè)按照您提供的offset和length參數(shù)值初始化位置和上界的緩沖區(qū)。
通過allocate()或者wrap()函數(shù)創(chuàng)建的緩沖區(qū)通常都是間接的糊秆。間接的緩沖區(qū)使用備份數(shù)組痘番,像我們之前討論的汞舱,您可以通過上面列出的API函數(shù)獲得對這些數(shù)組的存取權(quán)兵拢。Boolean型函數(shù)hasArray()告訴您這個(gè)緩沖區(qū)是否有一個(gè)可存取的備份數(shù)組。如果這個(gè)函數(shù)的返回true腻扇,array()函數(shù)會返回這個(gè)緩沖區(qū)對象所使用的數(shù)組存儲空間的引用砾嫉。
如果hasArray()函數(shù)返回false焕刮,不要調(diào)用array()函數(shù)或者arrayOffset()函數(shù)配并。如果您這樣做了您會得到一個(gè)UnsupportedOperationException異常畸冲。如果一個(gè)緩沖區(qū)是只讀的邑闲,它的備份數(shù)組將會是超出上界的,即使一個(gè)數(shù)組對象被提供給wrap()函數(shù)儡陨。調(diào)用array()函數(shù)或者arrayOffset()會拋出一個(gè)ReadOnlyBufferException異常褐筛,來阻止您得到存取權(quán)來修改只讀緩沖區(qū)的內(nèi)容渔扎。如果您通過其它的方式獲得了對備份數(shù)組的存取權(quán)限晃痴,對這個(gè)數(shù)組的修改也會直接影響到這個(gè)只讀緩沖區(qū)倘核。
最后一個(gè)函數(shù)紧唱,arrayOffset()蛹锰,返回緩沖區(qū)數(shù)據(jù)在數(shù)組中存儲的開始位置的偏移量(從數(shù)組頭0開始計(jì)算)铜犬。如果您使用了帶有三個(gè)參數(shù)的版本的wrap()函數(shù)來創(chuàng)建一個(gè)緩沖區(qū),對于這個(gè)緩沖區(qū)余爆,arrayOffset()會一直返回0呐粘,像我們之前討論的那樣作岖。然而痘儡,如果您切分了由一個(gè)數(shù)組提供存儲的緩沖區(qū)沉删,得到的緩沖區(qū)可能會有一個(gè)非0的數(shù)組偏移量。這個(gè)數(shù)組偏移量和緩沖區(qū)容量值會告訴您數(shù)組中哪些元素是被緩沖區(qū)使用的。
3货葬、復(fù)制緩沖區(qū)
緩沖區(qū)不限于管理數(shù)組中的外部數(shù)據(jù)休傍,它們也能管理其他緩沖區(qū)中的外部數(shù)據(jù)达传。當(dāng)一個(gè)管理其他緩沖器所包含的數(shù)據(jù)元素的緩沖器被創(chuàng)建時(shí)啸澡,這個(gè)緩沖器被稱為視圖緩沖器扎阶。
視圖存儲器總是通過調(diào)用已存在的存儲器實(shí)例中的函數(shù)來創(chuàng)建着饥。使用已存在的存儲器實(shí)例中的工廠方法意味著視圖對象為原始存儲器的你部實(shí)現(xiàn)細(xì)節(jié)私有宰掉。數(shù)據(jù)元素可以直接存取,無論它們是存儲在數(shù)組中還是以一些其他的方式挪拟,而不需經(jīng)過原始緩沖區(qū)對象的 get()/put() API。如果原始緩沖區(qū)是直接緩沖區(qū)惯雳,該緩沖區(qū)(視圖緩沖區(qū))的視圖會具有同樣的效率優(yōu)勢石景。
public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable {
public abstract CharBuffer duplicate( );
public abstract CharBuffer asReadOnlyBuffer( );
public abstract CharBuffer slice( );
}
Duplicate():
Duplicate()函數(shù)創(chuàng)建了一個(gè)與原始緩沖區(qū)相似的新緩沖區(qū)。兩個(gè)緩沖區(qū)共享數(shù)據(jù)元素恩商,擁有同樣的容量揽乱,但每個(gè)緩沖區(qū)擁有各自的位置,上界和標(biāo)記屬性陌粹。對一個(gè)緩沖區(qū)內(nèi)的數(shù)據(jù)元素所做的改變會反映在另外一個(gè)緩沖區(qū)上或舞。這一副本緩沖區(qū)具有與原始緩沖區(qū)同樣的數(shù)據(jù)視圖蒙幻。如果原始的緩沖區(qū)為只讀,或者為直接緩沖區(qū)抒和,新的緩沖區(qū)將繼承這些屬性范嘱。
asReadOnlyBuffer():
asReadOnlyBuffer() 函數(shù)來生成一個(gè)只讀的緩沖區(qū)視圖叠聋。這與duplicate() 相同受裹,除了這個(gè)新的緩沖區(qū)不允許使用 put(),并且其 isReadOnly() 函數(shù)將會返回true厦章。
如果一個(gè)只讀的緩沖區(qū)與一個(gè)可寫的緩沖區(qū)共享數(shù)據(jù)照藻,或者有包裝好的備份數(shù)組,那么對這個(gè)可寫的緩沖區(qū)或直接對這個(gè)數(shù)組的改變將反映在所有關(guān)聯(lián)的緩沖區(qū)上幸缕,包括只讀緩沖區(qū)。
slice():
分割緩沖區(qū)與復(fù)制相似熟妓,但 slice() 創(chuàng)建一個(gè)從原始緩沖區(qū)的當(dāng)前 position 開始的新緩沖區(qū),并且其容量是原始緩沖區(qū)的剩余元素?cái)?shù)量(limit - position)只恨。這個(gè)新緩沖區(qū)與原始緩沖區(qū)共享一段數(shù)據(jù)元素子序列告材。分割出來的緩沖區(qū)也會繼承只讀和直接屬性。slice() 分割緩沖區(qū):
4斥赋、字節(jié)緩沖區(qū)
4.1产艾、字節(jié)緩沖區(qū)
ByteBuffer 只是 Buffer 的一個(gè)子類隘膘,但字節(jié)緩沖區(qū)有字節(jié)的獨(dú)特之處管钳。字節(jié)緩沖區(qū)跟其他緩沖區(qū)類型最明顯的不同在于醇滥,它可以成為通道所執(zhí)行的 I/O 的源頭或目標(biāo)演闭,后面你會發(fā)現(xiàn)通道只接收 ByteBuffer 作為參數(shù)躲履。
字節(jié)是操作系統(tǒng)及其 I/O 設(shè)備使用的基本數(shù)據(jù)類型篷帅。當(dāng)在 JVM 和操作系統(tǒng)間傳遞數(shù)據(jù)時(shí)箭昵,將其他的數(shù)據(jù)類型拆分成構(gòu)成它們的字節(jié)是十分必要的泡一,系統(tǒng)層次的 I/O 面向字節(jié)的性質(zhì)可以在整個(gè)緩沖區(qū)的設(shè)計(jì)以及它們互相配合的服務(wù)中感受到矮瘟。同時(shí)埋涧,操作系統(tǒng)是在內(nèi)存區(qū)域中進(jìn)行 I/O 操作醇坝。這些內(nèi)存區(qū)域,就操作系統(tǒng)方面而言谚赎,是相連的字節(jié)序列。于是,毫無疑問钓觉,只有字節(jié)緩沖區(qū)有資格參與 I/O 操作批幌。
public abstract class ByteBuffer
extends Buffer implements Comparable {
public static ByteBuffer allocate (int capacity)
public static ByteBuffer allocateDirect (int capacity)
public abstract boolean isDirect( );
public static ByteBuffer wrap (byte[] array, int offset, int length)
public static ByteBuffer wrap (byte[] array)
public abstract ByteBuffer duplicate( );
public abstract ByteBuffer asReadOnlyBuffer( );
public abstract ByteBuffer slice( );
public final boolean hasArray( )
public final byte [] array( )
public final int arrayOffset( )
public abstract byte get( );
public abstract byte get (int index);
public ByteBuffer get (byte[] dst, int offset, int length)
public ByteBuffer get (byte[] dst, int offset, int length)
public abstract ByteBuffer put (byte b);
public abstract ByteBuffer put (int index, byte b);
public ByteBuffer put (ByteBuffer src)
public ByteBuffer put (byte[] src, int offset, int length)
public final ByteBuffer put (byte[] src)
public final ByteOrder order( )
public final ByteBuffer order (ByteOrder bo)
public abstract CharBuffer asCharBuffer( );
public abstract ShortBuffer asShortBuffer( );
public abstract IntBuffer asIntBuffer( );
public abstract LongBuffer asLongBuffer( );
public abstract FloatBuffer asFloatBuffer( );
public abstract DoubleBuffer asDoubleBuffer( );
public abstract char getChar( );
public abstract char getChar (int index);
public abstract ByteBuffer putChar (char value);
public abstract ByteBuffer putChar (int index, char value);
public abstract short getShort( );
public abstract short getShort (int index);
public abstract ByteBuffer putShort (short value);
public abstract ByteBuffer putShort (int index, short value);
public abstract int getInt( );
public abstract int getInt (int index);
public abstract ByteBuffer putInt (int value);
public abstract ByteBuffer putInt (int index, int value);
public abstract long getLong( );
public abstract long getLong (int index);
public abstract ByteBuffer putLong (long value);
public abstract ByteBuffer putLong (int index, long value);
public abstract float getFloat( );
public abstract float getFloat (int index);
public abstract ByteBuffer putFloat (float value);
public abstract ByteBuffer putFloat (int index, float value);
public abstract double getDouble( );
public abstract double getDouble (int index);
public abstract ByteBuffer putDouble (double value);
public abstract ByteBuffer putDouble (int index, double value);
public abstract ByteBuffer compact( );
public boolean equals (Object ob)
public int compareTo (Object ob)
public String toString( ) public int hashCode( )
}
多字節(jié)數(shù)值被存儲在內(nèi)存中的方式一般被稱為 endian-ness(字節(jié)順序)珊蟀。如果數(shù)字?jǐn)?shù)值的最高字節(jié) - big end(大端)儿子,位于低位地址(即 big end 先寫入內(nèi)存卒落,先寫入的內(nèi)存的地址是低位的腰湾,后寫入內(nèi)存的地址是高位的),那么系統(tǒng)就是大端字節(jié)順序两残。如果最低字節(jié)最先保存在內(nèi)存中沼死,那么系統(tǒng)就是小端字節(jié)順序崔赌。
ByteBuffer類有所不同:默認(rèn)字節(jié)順序總是ByteBuffer.BIG_ENDIAN,無論系統(tǒng)的固有字節(jié)順序是什么吟榴。Java的默認(rèn)字節(jié)順序是大端字節(jié)順序细移,這允許類文件等以及串行化的對象可以在任何JVM中工作速缨。如果固有硬件字節(jié)順序是小端摩骨,這會有性能隱患彤避。在使用固有硬件字節(jié)順序時(shí),將ByteBuffer的內(nèi)容當(dāng)作其他數(shù)據(jù)類型存榷巍(很快就會討論到)很可能高效得多。
ByteBuffer的字符順序設(shè)定可以隨時(shí)通過調(diào)用以ByteOrder.BIG_ENDIAN或ByteOrder.LITTL_ENDIAN為參數(shù)的order()函數(shù)來改變卒暂。
public abstract class ByteBuffer extends Buffer implements Comparable {
// This is a partial API listing
public final ByteOrder order( )
public final ByteBuffer order (ByteOrder bo)
}
如果一個(gè)緩沖區(qū)被創(chuàng)建為一個(gè)ByteBuffer對象的視圖也祠,那么order()返回的數(shù)值就是視圖被創(chuàng)建時(shí)其創(chuàng)建源頭的ByteBuffer的字節(jié)順序設(shè)定。視圖的字節(jié)順序設(shè)定在創(chuàng)建后不能被改變诈嘿,而且如果原始的字節(jié)緩沖區(qū)的字節(jié)順序在之后被改變,它也不會受到影響淳梦。
4.2遂蛀、直接緩沖區(qū)
內(nèi)核空間(與之相對的是用戶空間,如 JVM)是操作系統(tǒng)所在區(qū)域螃宙,它能與設(shè)備控制器(硬件)通訊所坯,控制著用戶區(qū)域進(jìn)程(如 JVM)的運(yùn)行狀態(tài)。最重要的是堂湖,所有的 I/O 都直接(物理內(nèi)存)或間接(虛擬內(nèi)存)通過內(nèi)核空間状土。
當(dāng)進(jìn)程(如 JVM)請求 I/O 操作的時(shí)候,它執(zhí)行一個(gè)系統(tǒng)調(diào)用將控制權(quán)移交給內(nèi)核斥季。當(dāng)內(nèi)核以這種方式被調(diào)用累驮,它隨即采取任何必要步驟,找到進(jìn)程所需數(shù)據(jù)谤专,并把數(shù)據(jù)傳送到用戶空間你的指定緩沖區(qū)置侍。內(nèi)核試圖對數(shù)據(jù)進(jìn)行高速緩存或預(yù)讀取拦焚,因此進(jìn)程所需數(shù)據(jù)可能已經(jīng)在內(nèi)核空間里了惕医。如果是這樣,該數(shù)據(jù)只需簡單地拷貝出來即可螟够。如果數(shù)據(jù)不在內(nèi)核空間峡钓,則進(jìn)程被掛起,內(nèi)核著手把數(shù)據(jù)讀進(jìn)內(nèi)存能岩。
從圖中你可能會覺得拉鹃,把數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間似乎有些多余。為什么不直接讓磁盤控制器把數(shù)據(jù)送到用戶空間的緩沖區(qū)呢膏燕?首先,硬件通常不能直接訪問用戶空間篷就。其次近忙,像磁盤這樣基于塊存儲的硬件設(shè)備操作的是固定大小的數(shù)據(jù)塊,而用戶進(jìn)程請求的可能是任意大小的或非對齊的數(shù)據(jù)塊未辆。在數(shù)據(jù)往來于用戶空間與存儲設(shè)備的過程中锯玛,內(nèi)核負(fù)責(zé)數(shù)據(jù)的分解、再組合工作,因此充當(dāng)著中間人的角色饭尝。
因此,操作系統(tǒng)是在內(nèi)存區(qū)域中進(jìn)行 I/O 操作实撒。這些內(nèi)存區(qū)域,就操作系統(tǒng)方面而言捷兰,是相連的字節(jié)序列负敏,這也意味著I/O操作的目標(biāo)內(nèi)存區(qū)域必須是連續(xù)的字節(jié)序列。在 JVM中顶考,字節(jié)數(shù)組可能不會在內(nèi)存中連續(xù)存儲(因?yàn)?JAVA 有 GC 機(jī)制)妖泄,或者無用存儲單元(會被垃圾回收)收集可能隨時(shí)對其進(jìn)行移動。
出于這個(gè)原因渊季,引入了直接緩沖區(qū)的概念罚渐。直接字節(jié)緩沖區(qū)通常是 I/O 操作最好的選擇。非直接字節(jié)緩沖區(qū)(即通過 allocate() 或 wrap() 創(chuàng)建的緩沖區(qū))可以被傳遞給通道搅轿,但是這樣可能導(dǎo)致性能損耗璧坟。通常非直接緩沖不可能成為一個(gè)本地 I/O 操作的目標(biāo)。
如果你向一個(gè)通道中傳遞一個(gè)非直接 ByteBuffer 對象用于寫入雀鹃,通道可能會在每次調(diào)用中隱含地進(jìn)行下面的操作:
- 創(chuàng)建一個(gè)臨時(shí)的直接ByteBuffer對象黎茎。
- 將非直接緩沖區(qū)的內(nèi)容復(fù)制到臨時(shí)緩沖中。
- 使用臨時(shí)緩沖區(qū)執(zhí)行低層次I/O操作傅瞻。
- 臨時(shí)緩沖區(qū)對象離開作用域嗅骄,并最終成為被回收的無用數(shù)據(jù)。
這可能導(dǎo)致緩沖區(qū)在每個(gè)I/O上復(fù)制并產(chǎn)生大量對象溺森,而這種事都是我們極力避免的。不過医窿,依靠工具,事情可以不這么糟糕卷要。運(yùn)行時(shí)間可能會緩存并重新使用直接緩沖區(qū)或者執(zhí)行其他一些聰明的技巧來提高吞吐量隔显。如果您僅僅為一次使用而創(chuàng)建了一個(gè)緩沖區(qū),區(qū)別并不是很明顯彪标。另一方面掷豺,如果您將在一段高性能腳本中重復(fù)使用緩沖區(qū),分配直接緩沖區(qū)并重新使用它們會使您游刃有余题画。
直接緩沖區(qū)時(shí)I/O的最佳選擇德频,但可能比創(chuàng)建非直接緩沖區(qū)要花費(fèi)更高的成本。直接緩沖區(qū)使用的內(nèi)存是通過調(diào)用本地操作系統(tǒng)方面的代碼分配的竞思,繞過了標(biāo)準(zhǔn)JVM堆棧钞护。建立和銷毀直接緩沖區(qū)會明顯比具有堆棧的緩沖區(qū)更加破費(fèi),這取決于主操作系統(tǒng)以及JVM實(shí)現(xiàn)课梳。直接緩沖區(qū)的內(nèi)存區(qū)域不受無用存儲單元收集支配余佃,因?yàn)樗鼈兾挥跇?biāo)準(zhǔn)JVM堆棧之外。
使用直接緩沖區(qū)或非直接緩沖區(qū)的性能權(quán)衡會因JVM椭懊,操作系統(tǒng)雾消,以及代碼設(shè)計(jì)而產(chǎn)生巨大差異。通過分配堆棧外的內(nèi)存狂窑,您可以使您的應(yīng)用程序依賴于JVM未涉及的其它力量桑腮。當(dāng)加入其他的移動部分時(shí),確定您正在達(dá)到想要的效果丛晦。我以一條舊的軟件行業(yè)格言建議您:先使其工作提陶,再加快其運(yùn)行。不要一開始就過多擔(dān)心優(yōu)化問題锌蓄;首先要注重正確性撑柔。JVM實(shí)現(xiàn)可能會執(zhí)行緩沖區(qū)緩存或其他的優(yōu)化,5這會在不需要您參與許多不必要工作的情況下為您提供所需的性能剪决。
直接ByteBuffer是通過調(diào)用具有所需容量的ByteBuffer.allocateDirect()函數(shù)產(chǎn)生的檀训,就像我們之前所涉及的allocate()函數(shù)一樣。注意用一個(gè)wrap()函數(shù)所創(chuàng)建的被包裝的緩沖區(qū)總是非直接的肢扯。
public abstract class ByteBuffer extends Buffer implements Comparable {
// This is a partial API listing
public static ByteBuffer allocate (int capacity)
public static ByteBuffer allocateDirect (int capacity)
public abstract boolean isDirect( );
}
所有的緩沖區(qū)都提供了一個(gè)叫做isDirect()的boolean函數(shù)妒茬,來測試特定緩沖區(qū)是否為直接緩沖區(qū)。雖然ByteBuffer是唯一可以被直接分配的類型蔚晨,但如果基礎(chǔ)緩沖區(qū)是一個(gè)直接ByteBuffer乍钻,對于非字節(jié)視圖緩沖區(qū),isDirect()可以是true铭腕。
4.3银择、視圖緩沖區(qū)
視圖緩沖區(qū)通過已存在的緩沖區(qū)對象實(shí)例的工廠方法來創(chuàng)建。這種視圖對象維護(hù)它自己的屬性累舷,容量浩考,位置,上界和標(biāo)記被盈,但是和原來的緩沖區(qū)共享數(shù)據(jù)元素搭伤。但是ByteBuffer類允許創(chuàng)建視圖來將byte型緩沖區(qū)字節(jié)數(shù)據(jù)映射為其它的原始數(shù)據(jù)類型。例如袜瞬,asLongBuffer()函數(shù)創(chuàng)建一個(gè)將八個(gè)字節(jié)型數(shù)據(jù)當(dāng)成一個(gè)long型數(shù)據(jù)來存取的視圖緩沖區(qū)怜俐。
下面列出的每一個(gè)工廠方法都在原有的ByteBuffer對象上創(chuàng)建一個(gè)視圖緩沖區(qū)。調(diào)用其中的任何一個(gè)方法都會創(chuàng)建對應(yīng)的緩沖區(qū)類型邓尤,這個(gè)緩沖區(qū)是基礎(chǔ)緩沖區(qū)的一個(gè)切分拍鲤,由基礎(chǔ)緩沖區(qū)的位置和上界決定。新的緩沖區(qū)的容量是字節(jié)緩沖區(qū)中存在的元素?cái)?shù)量除以視圖類型中組成一個(gè)數(shù)據(jù)類型的字節(jié)數(shù)汞扎。在切分中任一個(gè)超過上界的元素對于這個(gè)視圖緩沖區(qū)都是不可見的季稳。視圖緩沖區(qū)的第一個(gè)元素從創(chuàng)建它的ByteBuffer對象的位置開始(positon()函數(shù)的返回值)。具有能被自然數(shù)整除的數(shù)據(jù)元素個(gè)數(shù)的視圖緩沖區(qū)是一種較好的實(shí)現(xiàn)澈魄。
public abstract class ByteBuffer
extends Buffer implements Comparable {
// This is a partial API listing
public abstract CharBuffer asCharBuffer( );
public abstract ShortBuffer asShortBuffer( );
public abstract IntBuffer asIntBuffer( );
public abstract LongBuffer asLongBuffer( );
public abstract FloatBuffer asFloatBuffer( );
public abstract DoubleBuffer asDoubleBuffer( );
}
一旦您得到了視圖緩沖區(qū)景鼠,您可以用duplicate(),slice()和asReadOnlyBuffer()函數(shù)創(chuàng)建進(jìn)一步的子視圖一忱。
無論何時(shí)一個(gè)視圖緩沖區(qū)存取一個(gè)ByteBuffer的基礎(chǔ)字節(jié)莲蜘,這些字節(jié)都會根據(jù)這個(gè)視圖緩沖區(qū)的字節(jié)順序設(shè)定被包裝成一個(gè)數(shù)據(jù)元素。當(dāng)一個(gè)視圖緩沖區(qū)被創(chuàng)建時(shí)帘营,視圖創(chuàng)建的同時(shí)它也繼承了基礎(chǔ)ByteBuffer對象的字節(jié)順序設(shè)定票渠。這個(gè)視圖的字節(jié)排序不能再被修改。
當(dāng)直接從byte型緩沖區(qū)中采集數(shù)據(jù)時(shí)芬迄,視圖換沖突擁有提高效率的潛能问顷。如果這個(gè)視圖的字節(jié)順序和本地機(jī)器硬件的字節(jié)順序一致,低等級的(相對于高級語言而言)語言的代碼可以直接存取緩沖區(qū)中的數(shù)據(jù)值禀梳,而不是通過比特?cái)?shù)據(jù)的包裝和解包裝過程來完成杜窄。
4.4、數(shù)據(jù)元素視圖
ByteBuffer類提供了一個(gè)不太重要的機(jī)制來以多字節(jié)數(shù)據(jù)類型的形式存取byte數(shù)據(jù)組算途。ByteBuffer類為每一種原始數(shù)據(jù)類型提供了存取的和轉(zhuǎn)化的方法:
public abstract class ByteBuffer extends Buffer implements Comparable {
public abstract char getChar( );
public abstract char getChar (int index);
public abstract short getShort( );
public abstract short getShort (int index);
public abstract int getInt( );
public abstract int getInt (int index);
public abstract long getLong( );
public abstract long getLong (int index);
public abstract float getFloat( );
public abstract float getFloat (int index);
public abstract double getDouble( );
public abstract double getDouble (int index);
public abstract ByteBuffer putChar (char value);
public abstract ByteBuffer putChar (int index, char value);
public abstract ByteBuffer putShort (short value);
public abstract ByteBuffer putShort (int index, short value);
public abstract ByteBuffer putInt (int value);
public abstract ByteBuffer putInt (int index, int value);
public abstract ByteBuffer putLong (long value);
public abstract ByteBuffer putLong (int index, long value);
public abstract ByteBuffer putFloat (float value);
public abstract ByteBuffer putFloat (int index, float value);
public abstract ByteBuffer putDouble (double value);
public abstract ByteBuffer putDouble (int index, double value);
}
這些函數(shù)從當(dāng)前位置開始存取ByteBuffer的字節(jié)數(shù)據(jù)塞耕,就好像一個(gè)數(shù)據(jù)元素被存儲在那里一樣。根據(jù)這個(gè)緩沖區(qū)的當(dāng)前的有效的字節(jié)順序嘴瓤,這些字節(jié)數(shù)據(jù)會被排列或打亂成需要的原始數(shù)據(jù)類型扫外。
這段代碼:
int value = buffer.getInt( );
會返回一個(gè)由緩沖區(qū)中位置1-4的byte數(shù)據(jù)值組成的int型變量的值。實(shí)際的返回值取決于緩沖區(qū)的當(dāng)前的比特排序(byte-order)設(shè)置廓脆。更具體的寫法是:
int value = buffer.order (ByteOrder.BIG_ENDIAN).getInt( );
這將會返回值0x3BC5315E筛谚,同時(shí):
int value = buffer.order (ByteOrder.LITTLE_ENDIAN).getInt( );
返回值0x5E31C53B。
這些函數(shù)返回的元素不需要被任何特定模塊界限所限制6停忿。數(shù)值將會從以緩沖區(qū)的當(dāng)前位置開始的字節(jié)緩沖區(qū)中取出并組合驾讲,無論字組是否對齊。這樣的做法是低效的,但是它允許對一個(gè)字節(jié)流中的數(shù)據(jù)進(jìn)行隨機(jī)的放置吮铭。對于從二進(jìn)制文件數(shù)據(jù)或者包裝數(shù)據(jù)成特定平臺的格式或者導(dǎo)出到外部的系統(tǒng)时迫,這將是非常有用的。
Put函數(shù)提供與get相反的操作谓晌。原始數(shù)據(jù)的值會根據(jù)字節(jié)順序被分拆成一個(gè)個(gè)byte數(shù)據(jù)别垮。如果存儲這些字節(jié)數(shù)據(jù)的空間不夠,會拋出BufferOverflowException扎谎。每一個(gè)函數(shù)都有重載的和無參數(shù)的形式。重載的函數(shù)對位置屬性加上特定的字節(jié)數(shù)烧董。然后無參數(shù)的形式則不改變位置屬性毁靶。
5、Buffer類繼承關(guān)系
相關(guān)閱讀:
Reactor和Preactor模型 【http://www.reibang.com/p/b4de9b85c79d】
Selector【http://www.reibang.com/p/65157b97cc6e】
Channel【http://www.reibang.com/p/eb9d23113dfa】
IO相關(guān)基本概念【http://www.reibang.com/p/177021e33428】
參考書籍
Ron Hitchens 《Java NIO》