NIO 之 ByteBuffer實(shí)現(xiàn)原理

相關(guān)文章

IO巴碗、NIO檐什、AIO 內(nèi)部原理分析
NIO 之 Selector實(shí)現(xiàn)原理
NIO 之 Channel實(shí)現(xiàn)原理

前言

Java NIO 主要由下面3部分組成:

  • Buffer
  • Channel
  • Selector

在傳統(tǒng)IO中,流是基于字節(jié)的方式進(jìn)行讀寫的薯蝎。
在NIO中,使用通道(Channel)基于緩沖區(qū)數(shù)據(jù)塊的讀寫。

流是基于字節(jié)一個(gè)一個(gè)的讀取和寫入昵仅。
通道是基于塊的方式進(jìn)行讀取和寫入。

Buffer 類結(jié)構(gòu)圖

Buffer 的類結(jié)構(gòu)圖如下:


Buffer類結(jié)構(gòu)圖

從圖中發(fā)現(xiàn)java中8中基本的類型坞笙,除了boolean外岩饼,其它的都有特定的Buffer子類。

Buffer類分析

Filed

每個(gè)緩沖區(qū)都有這4個(gè)屬性薛夜,無論緩沖區(qū)是何種類型都有相同的方法來設(shè)置這些值

private int mark = -1;
private int position = 0;
private int limit;
private int capacity;

1. 標(biāo)記(mark)

初始值-1籍茧,表示未標(biāo)記。
標(biāo)記一個(gè)位置梯澜,方便以后reset重新從該位置讀取數(shù)據(jù)寞冯。

public final Buffer mark() {
    mark = position;
    return this;
}

public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}

2. 位置(position)

緩沖區(qū)中讀取或?qū)懭氲南乱粋€(gè)位置。這個(gè)位置從0開始晚伙,最大值等于緩沖區(qū)的大小

//獲取緩沖區(qū)的位置
public final int position() {
    return position;
}
//設(shè)置緩沖區(qū)的位置
public final Buffer position(int newPosition) {
    if ((newPosition > limit) || (newPosition < 0))
        throw new IllegalArgumentException();
    position = newPosition;
    if (mark > position) mark = -1;
    return this;
}

3. 限度(limit)

//獲取limit位置
public final int limit() {
    return limit;
}
//設(shè)置limit位置
public final Buffer limit(int newLimit) {
    if ((newLimit > capacity) || (newLimit < 0))
        throw new IllegalArgumentException();
    limit = newLimit;
    if (position > limit) position = limit;
    if (mark > limit) mark = -1;
    return this;
 }

4. 容量(capacity)

緩沖區(qū)可以保存元素的最大數(shù)量吮龄。該值在創(chuàng)建緩存區(qū)時(shí)指定,一旦創(chuàng)建完成后就不能修改該值咆疗。

//獲取緩沖區(qū)的容量
public final int capacity() {
    return capacity;
}

filp 方法

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}
  1. 將limit設(shè)置成當(dāng)前position的坐標(biāo)
  2. 將position設(shè)置為0
  3. 取消標(biāo)記

rewind 方法

public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}

從源碼中發(fā)現(xiàn)漓帚,rewind修改了position和mark,而沒有修改limit。

  1. 將position設(shè)置為0
  2. 取消mark標(biāo)記

clear 方法

    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
  1. 將position坐標(biāo)設(shè)置為0
  2. limit設(shè)置為capacity
  3. 取消標(biāo)記

從clear方法中午磁,我們發(fā)現(xiàn)Buffer中的數(shù)據(jù)沒有清空尝抖,如果通過Buffer.get(i)的方式還是可以訪問到數(shù)據(jù)的。如果再次向緩沖區(qū)中寫入數(shù)據(jù)迅皇,他會(huì)覆蓋之前存在的數(shù)據(jù)昧辽。

remaining 方法

查看當(dāng)前位置和limit之間的元素?cái)?shù)。

public final int remaining() {
    return limit - position;
}

hasRemaining 方法

判斷當(dāng)前位置和limit之間是否還有元素

public final boolean hasRemaining() {
    return position < limit;
}

ByteBuffer 類分析

ByteBuffer類結(jié)果圖

從圖中我們可以發(fā)現(xiàn) ByteBuffer繼承于Buffer類登颓,ByteBuffer是個(gè)抽象類搅荞,它有兩個(gè)實(shí)現(xiàn)的子類HeapByteBuffer和MappedByteBuffer類

HeapByteBuffer:在堆中創(chuàng)建的緩沖區(qū)。就是在jvm中創(chuàng)建的緩沖區(qū)框咙。
MappedByteBuffer:直接緩沖區(qū)咕痛。物理內(nèi)存中創(chuàng)建緩沖區(qū),而不在堆中創(chuàng)建扁耐。

allocate 方法(創(chuàng)建堆緩沖區(qū))

public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
}

我們發(fā)現(xiàn)allocate方法創(chuàng)建的緩沖區(qū)是創(chuàng)建的HeapByteBuffer實(shí)例暇检。

HeapByteBuffer 構(gòu)造

HeapByteBuffer(int cap, int lim) {            // package-private
    super(-1, 0, lim, cap, new byte[cap], 0);
}

從堆緩沖區(qū)中看出,所謂堆緩沖區(qū)就是在堆內(nèi)存中創(chuàng)建一個(gè)byte[]數(shù)組婉称。

allocateDirect創(chuàng)建直接緩沖區(qū)

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

我們發(fā)現(xiàn)allocate方法創(chuàng)建的緩沖區(qū)是創(chuàng)建的DirectByteBuffer實(shí)例块仆。

DirectByteBuffer構(gòu)造

DirectByteBuffer 構(gòu)造方法

直接緩沖區(qū)是通過java中Unsafe類進(jìn)行在物理內(nèi)存中創(chuàng)建緩沖區(qū)构蹬。

wrap 方法

public static ByteBuffer wrap(byte[] array)
public static ByteBuffer wrap(byte[] array, int offset, int length);

可以通過wrap類把字節(jié)數(shù)組包裝成緩沖區(qū)ByteBuffer實(shí)例。
這里需要注意的的悔据,把a(bǔ)rray的引用賦值給ByteBuffer對(duì)象中字節(jié)數(shù)組庄敛。如果array數(shù)組中的值更改,則ByteBuffer中的數(shù)據(jù)也會(huì)更改的科汗。

get 方法

  1. public byte get()
    獲取position坐標(biāo)元素藻烤,并將position+1;
  2. public byte get(int i)
    獲取指定索引下標(biāo)的元素
  3. public ByteBuffer get(byte[] dst)
    從當(dāng)前position中讀取元素填充到dst數(shù)組中头滔,每填充一個(gè)元素position+1;
  4. public ByteBuffer get(byte[] dst, int offset, int length)
    從當(dāng)前position中讀取元素到dst數(shù)組的offset下標(biāo)開始填充length個(gè)元素怖亭。

put 方法

  1. public ByteBuffer put(byte x)
    寫入一個(gè)元素并position+1
  2. public ByteBuffer put(int i, byte x)
    指定的索引寫入一個(gè)元素
  3. public final ByteBuffer put(byte[] src)
    寫入一個(gè)自己數(shù)組,并position+數(shù)組長(zhǎng)度
  4. public ByteBuffer put(byte[] src, int offset, int length)
    從一個(gè)自己數(shù)組的offset開始length個(gè)元素寫入到ByteBuffer中坤检,并把position+length
  5. public ByteBuffer put(ByteBuffer src)
    寫入一個(gè)ByteBuffer兴猩,并position加入寫入的元素個(gè)數(shù)

視圖緩沖區(qū)

Paste_Image.png

ByteBuffer可以轉(zhuǎn)換成其它類型的Buffer。例如CharBuffer早歇、IntBuffer 等倾芝。

壓縮緩沖區(qū)

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

1、把緩沖區(qū)positoin到limit中的元素向前移動(dòng)positoin位
2箭跳、設(shè)置position為remaining()
3晨另、 limit為緩沖區(qū)容量
4、取消標(biāo)記

例如:ByteBuffer.allowcate(10);
內(nèi)容:[0 ,1 ,2 ,3 4, 5, 6, 7, 8, 9]

compact前

[0 ,1 ,2 , 3, 4, 5, 6, 7, 8, 9]
pos=4
lim=10
cap=10

compact后

[4, 5, 6, 7, 8, 9, 6, 7, 8, 9]
pos=6
lim=10
cap=10

slice方法

public ByteBuffer slice() {
        return new HeapByteBuffer(hb,
                    -1,
                    0,
                    this.remaining(),
                    this.remaining(),
                    this.position() + offset);
}

創(chuàng)建一個(gè)分片緩沖區(qū)谱姓。分配緩沖區(qū)與主緩沖區(qū)共享數(shù)據(jù)借尿。
分配的起始位置是主緩沖區(qū)的position位置
容量為limit-position。
分片緩沖區(qū)無法看到主緩沖區(qū)positoin之前的元素屉来。

直接緩沖區(qū)和堆緩沖區(qū)性能對(duì)比

下面我們從緩沖區(qū)創(chuàng)建的性能和讀取性能兩個(gè)方面進(jìn)行性能對(duì)比垛玻。

讀寫性能對(duì)比

public static void directReadWrite() throws Exception {
    int time = 10000000;
    long start = System.currentTimeMillis();
    ByteBuffer buffer = ByteBuffer.allocate(4*time);
    for(int i=0;i<time;i++){
        buffer.putInt(i);
    }
    buffer.flip();
    for(int i=0;i<time;i++){
        buffer.getInt();
    }
    System.out.println("堆緩沖區(qū)讀寫耗時(shí)  :"+(System.currentTimeMillis()-start));
    
    start = System.currentTimeMillis();
    ByteBuffer buffer2 = ByteBuffer.allocateDirect(4*time);
    for(int i=0;i<time;i++){
        buffer2.putInt(i);
    }
    buffer2.flip();
    for(int i=0;i<time;i++){
        buffer2.getInt();
    }
    System.out.println("直接緩沖區(qū)讀寫耗時(shí):"+(System.currentTimeMillis()-start));
}

輸出結(jié)果:

堆緩沖區(qū)創(chuàng)建耗時(shí)  :70
直接緩沖區(qū)創(chuàng)建耗時(shí):47

從結(jié)果中我們發(fā)現(xiàn)堆緩沖區(qū)讀寫比直接緩沖區(qū)讀寫耗時(shí)更長(zhǎng)。

public static void directAllocate() throws Exception {
    int time = 10000000;
    long start = System.currentTimeMillis();
    for (int i = 0; i < time; i++) {
        ByteBuffer buffer = ByteBuffer.allocate(4);
    }
    System.out.println("堆緩沖區(qū)創(chuàng)建時(shí)間:"+(System.currentTimeMillis()-start));
        
    start = System.currentTimeMillis();
    for (int i = 0; i < time; i++) {
        ByteBuffer buffer = ByteBuffer.allocateDirect(4);
    }
    System.out.println("直接緩沖區(qū)創(chuàng)建時(shí)間:"+(System.currentTimeMillis()-start));
}

輸出結(jié)果:

堆緩沖區(qū)創(chuàng)建時(shí)間:73
直接緩沖區(qū)創(chuàng)建時(shí)間:5146

從結(jié)果中發(fā)現(xiàn)直接緩沖區(qū)創(chuàng)建分配空間比較耗時(shí)奶躯。

對(duì)比結(jié)論

直接緩沖區(qū)比較適合讀寫操作,最好能重復(fù)使用直接緩沖區(qū)并多次讀寫的操作亿驾。
堆緩沖區(qū)比較適合創(chuàng)建新的緩沖區(qū)嘹黔,并且重復(fù)讀寫不會(huì)太多的應(yīng)用。

建議:如果經(jīng)過性能測(cè)試莫瞬,發(fā)現(xiàn)直接緩沖區(qū)確實(shí)比堆緩沖區(qū)效率高才使用直接緩沖區(qū)儡蔓,否則不建議使用直接緩沖區(qū)。


想了解更多精彩內(nèi)容請(qǐng)關(guān)注我的公眾號(hào)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疼邀,一起剝皮案震驚了整個(gè)濱河市喂江,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌旁振,老刑警劉巖获询,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涨岁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吉嚣,警方通過查閱死者的電腦和手機(jī)梢薪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尝哆,“玉大人秉撇,你說我怎么就攤上這事∏镄梗” “怎么了琐馆?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)恒序。 經(jīng)常有香客問我瘦麸,道長(zhǎng),這世上最難降的妖魔是什么奸焙? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任瞎暑,我火速辦了婚禮,結(jié)果婚禮上与帆,老公的妹妹穿的比我還像新娘了赌。我一直安慰自己,他們只是感情好玄糟,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布勿她。 她就那樣靜靜地躺著,像睡著了一般阵翎。 火紅的嫁衣襯著肌膚如雪逢并。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天郭卫,我揣著相機(jī)與錄音砍聊,去河邊找鬼。 笑死贰军,一個(gè)胖子當(dāng)著我的面吹牛玻蝌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播词疼,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼俯树,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了贰盗?” 一聲冷哼從身側(cè)響起许饿,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舵盈,沒想到半個(gè)月后陋率,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體球化,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年翘贮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赊窥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狸页,死狀恐怖锨能,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芍耘,我是刑警寧澤址遇,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站斋竞,受9級(jí)特大地震影響倔约,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坝初,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一浸剩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鳄袍,春花似錦绢要、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哀九,卻和暖如春剿配,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阅束。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工呼胚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人息裸。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓砸讳,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親界牡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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

  • 轉(zhuǎn)自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的貓閱讀 2,277評(píng)論 0 22
  • Java NIO(New IO)是從Java 1.4版本開始引入的一個(gè)新的IO API漾抬,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,536評(píng)論 1 143
  • Buffer java NIO庫是在jdk1.4中引入的宿亡,NIO與IO之間的第一個(gè)區(qū)別在于,IO是面向流的纳令,而NI...
    德彪閱讀 2,189評(píng)論 0 3
  • ChannelChannel CharacteristicsJava NIO Channel Classesbuf...
    六尺帳篷閱讀 3,907評(píng)論 2 28
  • 緩沖區(qū) 緩沖區(qū)的兩個(gè)重要組件:狀態(tài)變量和訪問方法 (accessor)挽荠。 狀態(tài)變量 每一個(gè)讀/寫操作都會(huì)改變緩沖...
    甚了閱讀 537評(píng)論 0 1