關(guān)于NIO Buffer基本原理詳解

關(guān)于NIO Buffer中4個(gè)重要狀態(tài)屬性的含義:position、limit更舞、capacity與mark
Buffer本身是一個(gè)容器坎缭,稱作緩沖區(qū),里面包裝了特定的一種原生類型盘榨,其子類包括ByteBuffer、CharBuffer蟆融、LongBuffer草巡、IntBuffer、DoubleBuffer型酥、ShortBuffer山憨、FloatBuffer. 注意是沒有Boolean類型的Buffer
Buffer是一種特定原生類型線性的有限的元素序列,Buffer中比較重要的4個(gè)屬性:position弥喉、limit郁竟、capacity、mark. 在使用 Buffer 時(shí)由境,我們實(shí)際操作的就是這四個(gè)屬性的值.
具體介紹下4個(gè)屬性:

  • capacity(容量):一個(gè)buffer能夠容納數(shù)據(jù)元素的最大數(shù)量棚亩,capacity不會(huì)為負(fù)數(shù),且永遠(yuǎn)不能被改變.
    假設(shè):IntBuffer.allocate(1024), 分配了大小為1024的元素個(gè)數(shù)虏杰,則capacity就等于1024.
  • limit(上界):一個(gè)buffer的limit指的是第一個(gè)不能在讀也不能在寫的元素索引. limit不會(huì)為負(fù)數(shù)讥蟆,并且一定是小于capacity的.
    假設(shè):IntBuffer.allocate(1024), 我們?cè)诔绦蛑性O(shè)置limit=512,說明Buffer的容量是1024嘹屯,但是從512之后既不能讀也不能寫了攻询,進(jìn)一步說明該Buffer的實(shí)際可用大小是512.
  • position(位置):一個(gè)buffer的position指的是下一個(gè)將要讀或者寫的元素的索引.position不會(huì)為負(fù)數(shù),并且一定是小于limit的. position的位置主要由get()和put()方法的調(diào)用來更新.
  • mark(標(biāo)記):一個(gè)備忘地址州弟,作為臨時(shí)標(biāo)記位置使用钧栖,標(biāo)記在設(shè)定前是未定義的.
    mark的使用場(chǎng)景:
    假設(shè)IntBuffer.allocate(1024)低零,現(xiàn)在position位置為10,現(xiàn)在只想發(fā)送512到1024之間的緩沖數(shù)據(jù)拯杠,此時(shí)我們可以buffer.mark(buffer.position())既將position記入mark位置掏婶,然后buffer.postion(512),此時(shí)發(fā)送的數(shù)據(jù)就是512到1024之間的數(shù)據(jù)潭陪。發(fā)送完成后雄妥,調(diào)用buffer.reset()將mark臨時(shí)標(biāo)記賦值給position使得position=mark。注意如果未設(shè)定mark依溯,而調(diào)用了buffer.reset()方法則會(huì)拋出InvalidMarkException.

不變式:
0 <= mark <= position <= limit <= capacity

傳輸數(shù)據(jù):
Buffer中的每個(gè)子類中都有g(shù)et()和put()方法.
帶參數(shù)的put和get方法稱作絕對(duì)存入/取出老厌,位置是通過參數(shù)指定的.
絕對(duì)操作不影響position位置,但是如果索引位置超出limit黎炉,則會(huì)拋出IndexOutOfBoundsException枝秤;
不帶參數(shù)的put和get稱作相對(duì)存入/取出,即position位置自動(dòng)前進(jìn).
對(duì)于get相對(duì)操作慷嗜,如果位置超過了limit淀弹,則會(huì)拋出BufferUnderflowException;
對(duì)于put相對(duì)操作庆械,如果位置超過了limit薇溃,則會(huì)拋出BufferOverflowException;

線程安全性:
buffer在多線程并發(fā)下并不是安全的缭乘。如果一個(gè)buffer會(huì)在多個(gè)線程使用沐序,那么需要使用恰當(dāng)?shù)耐讲僮鱽碓L問buffer。也就是buffer本身并不是線程安全的忿峻。

常用方法:
這部分我們以實(shí)際代碼為例來說明:
clear()方法薄啥,清除,將buffer重置為空狀態(tài)逛尚,它并不會(huì)更改緩沖區(qū)內(nèi)的任何數(shù)據(jù)元素. 如果此時(shí)還沒有讀取的數(shù)據(jù),則就無法讀取到了.

public final Buffer clear() {
        position = 0; // 位置重置為0
        limit = capacity; // limit重置為capacity
        mark = -1; // 丟棄標(biāo)記
        return this;
}

flip()方法刁愿,翻轉(zhuǎn)绰寞,使buffer從寫模式轉(zhuǎn)換到讀模式.

public final Buffer flip() {
        limit = position; // 將limit設(shè)置為position
        position = 0; // position重置為0
        mark = -1; // 丟棄標(biāo)記
        return this;
}

rewind()方法,重繞铣口,重置position為0滤钱,limit保持不變,此時(shí)調(diào)用rewind前buffer已處于讀模式下了, 可以重新讀取buffer中的數(shù)據(jù).

public final Buffer rewind() {
        position = 0; // 重置position為0
        mark = -1; // 丟棄標(biāo)記 
        return this;
}

compact()方法脑题,將所有未讀的數(shù)據(jù)拷貝到Buffer起始處. 然后將position設(shè)到最后一個(gè)未讀元素正后面. limit屬性依然像clear()方法一樣件缸,設(shè)置成capacity. 現(xiàn)在Buffer準(zhǔn)備好寫數(shù)據(jù)了,但是不會(huì)覆蓋未讀的數(shù)據(jù).
注意compact方法的實(shí)現(xiàn)是由原生類型的子類實(shí)現(xiàn)叔遂,比如ByteBuffer則由HeapByteBuffer中實(shí)現(xiàn).

public ByteBuffer compact() {
        System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
        position(remaining());
        limit(capacity());
        discardMark();
        return this;
 }

hasRemaining()方法他炊,會(huì)在讀取緩沖區(qū)時(shí)告訴你是否已經(jīng)達(dá)到緩沖區(qū)的上界. 可以通過remaining()高效讀取buffer數(shù)據(jù).

int count = buffer.remaining();  
for (int i = 0; i < count; i++) {  
    myByteArray[i] = buffer.get();  
} 

slice()方法争剿,是對(duì)原有數(shù)據(jù)的一個(gè)快照,共享相同的底層數(shù)據(jù)元素. 調(diào)用slice方法后痊末,會(huì)得到大于等于position且小于limit之間的數(shù)據(jù)蚕苇,對(duì)于改變slice方法獲得大buffer數(shù)據(jù),也能夠反映到原buffer上凿叠。
舉個(gè)例子:

public class NioTest6_slice {
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        int capacity = byteBuffer.capacity();
        for (int i = 0 ; i < capacity; i++) {
            byteBuffer.put((byte)i);
        }

        // 設(shè)置position涩笤、limit
        byteBuffer.position(2);
        byteBuffer.limit(6);

        //slice方法是前閉后開的 大于等于position,小于limit
        ByteBuffer subByteBuffer = byteBuffer.slice();

        // 改變subByteBuffer內(nèi)容盒件,也能反映到byteBuffer上
        for (int j = 0; j < subByteBuffer.capacity(); j++) {
            // 2到5位置的數(shù)*2
            subByteBuffer.put((byte)(2 * subByteBuffer.get(j)));
        }

        // 設(shè)置回原來的值蹬碧,打印輸出看下byteBuffer數(shù)據(jù)變化
        byteBuffer.position(0);
        byteBuffer.limit(capacity);
        while (byteBuffer.hasRemaining()) {
            System.out.println(byteBuffer.get());
        }
    }
}

輸出結(jié)果中看到第3個(gè)位置到第5個(gè)位置的數(shù)據(jù)都??2了,返回4,6,8.

0
1
4
6 
8 
10
6
7
8
9

duplicate()方法炒刁,創(chuàng)建了一個(gè)與原始緩沖區(qū)相似的新緩沖區(qū)锰茉,與調(diào)用slice方法一樣也是共享相同的底層數(shù)據(jù)元素, 擁有同樣的容量, 但每個(gè)緩沖區(qū)擁有各自的 position、limit 和 mark 屬性.

public class NioTest6_duplicate {
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        int capacity = byteBuffer.capacity();
        for (int i = 0 ; i < capacity; i++) {
            byteBuffer.put((byte)i);
        }

        ByteBuffer subByteBuffer = byteBuffer.duplicate();
        subByteBuffer.position(0); // 單獨(dú)給duplicate出來的buffer設(shè)置position
        System.out.println("byteBuffer position:" + byteBuffer.position() + "--subByteBuffer position:" + subByteBuffer.position());

        // 改變subByteBuffer內(nèi)容切心,也能反映到byteBuffer上
        for (int j = 0; j < subByteBuffer.capacity(); j++) {
            // 2到5位置的數(shù)*2
            subByteBuffer.put((byte)(2 * subByteBuffer.get(j)));
        }

        // 切換到讀模式
        byteBuffer.flip();
        while (byteBuffer.hasRemaining()) {
            System.out.println(byteBuffer.get());
        }
    }
}

輸出結(jié)果能看到原buffer數(shù)據(jù)元素變化

byteBuffer position:10--subByteBuffer position:0
0
2
4
6
8
10
12
14
16
18

asReadOnlyBuffer()方法飒筑,我們可以隨時(shí)將一個(gè)普通的Buffer調(diào)用asReadOnlyBuffer方法返回一個(gè)只讀Buffer. 但是,不能將一個(gè)只讀Buffer轉(zhuǎn)換為讀寫B(tài)uffer.

關(guān)于Buffer的Scattering 和 Gathering
scatter / gather經(jīng)常用于需要將傳輸?shù)臄?shù)據(jù)分開處理的場(chǎng)合绽昏,例如傳輸一個(gè)由消息頭和消息體組成的消息协屡,你可能會(huì)將消息體和消息頭分散到不同的buffer中,這樣你可以方便的處理消息頭和消息體.
Scattering:
Scattering Reads在移動(dòng)下一個(gè)buffer前全谤,必須填滿當(dāng)前的buffer肤晓,這也意味著它不適用于動(dòng)態(tài)消息(譯者注:消息大小不固定). 換句話說,如果存在消息頭和消息體认然,消息頭必須完成填充(例如 128byte)补憾,Scattering Reads才能正常工作。
Gattering:
buffer數(shù)組是write()方法的入?yún)⒕碓保瑆rite()方法會(huì)按照buffer在數(shù)組中的順序盈匾,將數(shù)據(jù)寫入到channel,注意只有position和limit之間的數(shù)據(jù)才會(huì)被寫入毕骡。
因此削饵,如果一個(gè)buffer的容量為128byte,但是僅僅包含58byte的數(shù)據(jù)未巫,那么這58byte的數(shù)據(jù)將被寫入到channel中窿撬。因此與Scattering Reads相反,Gathering Writes能較好的處理動(dòng)態(tài)消息.
舉個(gè)例子:

public class NioTest10_scatteringandgathering {

    public static void main(String[] args) throws  Exception {
        try(ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) {
            InetSocketAddress inetSocketAddress = new InetSocketAddress(8899);
            serverSocketChannel.socket().bind(inetSocketAddress); // 綁定到8899端口


            int messageLength = 2 + 3 + 4;

            ByteBuffer[] byteBuffers = new ByteBuffer[3];
            byteBuffers[0] = ByteBuffer.allocate(2);
            byteBuffers[1] = ByteBuffer.allocate(3);
            byteBuffers[2] = ByteBuffer.allocate(4);

            SocketChannel socketChannel = serverSocketChannel.accept();

            do {
                int byteRead = 0;
                while (byteRead < messageLength) {
                    long r = socketChannel.read(byteBuffers);
                    System.out.println("--------------r:" + r);
                    byteRead += r;

                    System.out.println("byteRead:" + byteRead);

                    Arrays.asList(byteBuffers).stream().map(buffer -> "position:" + buffer.position() + ", limit:" + buffer.limit())
                            .forEach(System.out::println);
                }

                Arrays.asList(byteBuffers).forEach(Buffer::flip);

                // 實(shí)際寫的個(gè)數(shù)
                int byteWrites = 0;
                while (byteWrites < messageLength) {
                    long r = socketChannel.write(byteBuffers);
                    byteWrites += r;
                }

                Arrays.asList(byteBuffers).forEach(Buffer::clear);

                System.out.println("byteRead:" + byteRead + ", byteWrite:" + byteWrites + ", messageLength:" + messageLength);
            } while (true);
        }
    }
}

以上服務(wù)啟動(dòng)后叙凡,我們通過nc localhost 8899或者telecom localhost 8899,然后輸入Hello world字符串劈伴,在控制臺(tái)上能看到輸出:

--------------r:9
byteRead:9
position:2, limit:2
position:3, limit:3
position:4, limit:4
byteRead:9, byteWrite:9, messageLength:9

參考文章:
http://zachary-guo.iteye.com/blog/1457542
http://www.reibang.com/p/1af407c043cb

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市握爷,隨后出現(xiàn)的幾起案子跛璧,更是在濱河造成了極大的恐慌严里,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赡模,死亡現(xiàn)場(chǎng)離奇詭異田炭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)漓柑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門教硫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辆布,你說我怎么就攤上這事瞬矩。” “怎么了锋玲?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵景用,是天一觀的道長。 經(jīng)常有香客問我惭蹂,道長伞插,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任盾碗,我火速辦了婚禮媚污,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘廷雅。我一直安慰自己耗美,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布航缀。 她就那樣靜靜地躺著商架,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芥玉。 梳的紋絲不亂的頭發(fā)上蛇摸,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音飞傀,去河邊找鬼皇型。 笑死,一個(gè)胖子當(dāng)著我的面吹牛砸烦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绞吁,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼幢痘,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了家破?” 一聲冷哼從身側(cè)響起颜说,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤购岗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后门粪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喊积,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年玄妈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乾吻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拟蜻,死狀恐怖绎签,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酝锅,我是刑警寧澤诡必,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站搔扁,受9級(jí)特大地震影響爸舒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜稿蹲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一扭勉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧场绿,春花似錦剖效、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至熬拒,卻和暖如春爷光,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背澎粟。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工蛀序, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人活烙。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓徐裸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親啸盏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子重贺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個(gè)新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,557評(píng)論 1 143
  • 轉(zhuǎn)自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的貓閱讀 2,313評(píng)論 0 22
  • Java NIO中的Buffer用于和NIO通道進(jìn)行交互。如你所知气笙,數(shù)據(jù)是從通道讀入緩沖區(qū)次企,從緩沖區(qū)寫入到通道中的...
    AFinalStone閱讀 277評(píng)論 0 0
  • Buffer java NIO庫是在jdk1.4中引入的,NIO與IO之間的第一個(gè)區(qū)別在于潜圃,IO是面向流的缸棵,而NI...
    德彪閱讀 2,211評(píng)論 0 3
  • 美國前總統(tǒng)吉米·卡特,90歲高齡谭期,于8月20日承認(rèn)自己腦部有四處黑色素瘤病灶堵第,正在進(jìn)行放療治療,這次親口承認(rèn)證實(shí)了...
    許遠(yuǎn)山閱讀 673評(píng)論 0 1