Buffer/ByteBuffer/ByteBuf詳解

ByteBuffer

ByteBuffer是一個抽象類狼讨,NIO編程中經(jīng)常會使用,Netty常用的ByteBuf實際上也是對其的一種封裝

  • Byte即字節(jié)系羞,一個8位的二進制
  • Buffer即緩沖區(qū)郭计,所謂緩沖區(qū),其實就是一個臨時存儲數(shù)據(jù)的容器(可以理解為一個數(shù)組)椒振,而且一般可以重用

緩沖區(qū)

緩沖區(qū)有什么用吶?

  • 減少實際的物理讀寫次數(shù)
  • 緩沖區(qū)創(chuàng)建時分配固定內(nèi)存昭伸,這塊內(nèi)存區(qū)域可被重用,減少動態(tài)分配和回收內(nèi)存的次數(shù)

舉個簡單的例子 比如我們?nèi)ト】爝f(數(shù)據(jù))澎迎,快遞很多庐杨,一次只能取一個选调,那我們就需要來回跑很多趟(實際讀寫次數(shù))
加入我們有個大筐,一次把快遞全裝回來灵份,就省了不少事
這個大筐在這個過程就扮演一個“緩沖區(qū)”的作用仁堪,下次取快遞還能用

byte[]

Buffer類是JDK1.4引入的NIO包中定義的一個抽象類,那我們先看看1.4之前一般是如何從管道獲取數(shù)據(jù)的填渠,大概寫法如下:

byte[] bytes = new byte[1024];
int read = clientSocket.getInputStream().read(bytes);
System.out.println("received data:" + new String(bytes, 0, read));

我們接收IO流字節(jié)數(shù)據(jù)的方式是用一個byte[]來保存弦聂,這個byte[]其實已經(jīng)起到一個緩沖區(qū)的作用,就是用起來不太方便氛什,也不好重復(fù)利用

而NIO出版的ByteBuffer可以理解為對byte[]的一個封裝莺葫,使其更易用于臨時數(shù)據(jù)緩沖場景

ByteBuffer繼承自Buffer類,Buffer類就是對緩沖區(qū)的一種抽象枪眉,讓我們看看作為一個Buffer有哪些特性

Buffer

Buffer是一個線性的捺檬、有界、方便重用的容器

屬性

它有4個重點屬性瑰谜,capacity欺冀,limit树绩,position萨脑,mark,我不先介紹其含義饺饭,從實際使用角度闡述為什么需要這4個屬性

首先作為一個有界容器渤早,那肯定是要明確標(biāo)識界限的,這樣可以知道容器到底有多大瘫俊,需要開辟多少空間鹊杖,所以需要有個capacity代表容器的容量

作為一個線性容器,使用者希望寫方法只要告訴容器寫的是什么即可扛芽,而不用像數(shù)組一樣需要指定index骂蓖,取方法也一樣,取完某一個再次取就接著取下一個川尖,不需要指定index登下,所以就需要有個屬性來標(biāo)識當(dāng)前讀/寫的位置,即position叮喳,每次讀/寫結(jié)束被芳,直接把position向后移動一位,下一次讀/寫就是下一個元素

position

Buffer支持讀取操作時需要知道總共有多少可讀馍悟,這個值并非capacity畔濒,因為容器可能未滿,同時寫操作锣咒,由于Buffer可重復(fù)利用侵状,每次的最大可寫量也并不一定是capacity赞弥,這兩種需求都需要有個讀寫界限值,用limit標(biāo)識

有時候我們需要從某個位置讀完數(shù)據(jù)可能過一會又想從之前哪個位置重新讀取一次趣兄,但關(guān)鍵我們的Buffer是線性的嗤攻,position只能增不能減,如何找到之前的位置诽俯?所以buffer提供了一個mark屬性讓使用者可以標(biāo)識之前的一個位置妇菱,并提供mark()方法讓mark值等于position,讀/寫一段時間postion值變大了暴区,可以調(diào)用reset()方法闯团,讓postion回到mark的值,這就可以重新從mark點位讀取了

mark
方法

上文已介紹兩個針對mark屬性的方法:mark()rest()仙粱,除此之外還有幾個方便的方法:

clear() :清空的意思房交,清空后就可以再次利用,所以說buffer很方便重用伐割,clear方法把limit=capacity候味,position=0,mark=-1(置空)隔心,為了重新寫入做好準(zhǔn)備(實際上并沒有清空元素)
flip():字面意思翻轉(zhuǎn)白群,實際實現(xiàn)是limit=position,position=0硬霍,為讀取做好準(zhǔn)備帜慢,一般是一個Buffer寫完數(shù)據(jù)后轉(zhuǎn)換為讀模式時使用,所以名字叫翻轉(zhuǎn)還是很貼切唯卖,翻轉(zhuǎn)時limit=position記錄了當(dāng)前寫到的最大位置粱玲,也是可讀的最大位置,而position=0從頭開始讀
rewind:倒帶拜轨,主要為了重新讀抽减,實現(xiàn)是position=0

HeapByteBuffer

講完Buffer再次回到抽象類ByteBuffer,顧名思義橄碾,就是一個存字節(jié)的Buffer卵沉,他的一個重要屬性:hb

HeapByteBuffer

就是被ByteBuffer封裝的byte數(shù)組,而后面的注釋說只有heap buffers使用這個屬性堪嫂,實現(xiàn)代表就是HeapByteBuffer偎箫,Heap代表了這種Buffer的實際存儲地址是在堆內(nèi)存中,就是hb屬性指向的堆內(nèi)存空間

那還有什么存儲方式吶皆串,就要介紹ByteBuffer的另一個實現(xiàn)DirectByteBuffer

DirectByteBuffer

DirectByteBuffer作為ByteBuffer自然是一個臨時存儲Byte的容器淹办,但它的數(shù)據(jù)不存儲在堆里,那么還能怎么存恶复?存磁盤嗎怜森?

存磁盤顯然是不可能速挑,那慢死了,實際上DirectByteBuffer內(nèi)的字節(jié)還是要存儲在物理內(nèi)存中副硅,只不過并不屬于java虛擬機運行時數(shù)據(jù)區(qū)的一部分姥宝,而是直接內(nèi)存,也叫堆外內(nèi)存

直接內(nèi)存

上圖中HeapByteBuffer也是我們最常用的方式指向?qū)χ袃?nèi)存byte[]的地址恐疲,當(dāng)讀取IO數(shù)據(jù)時先把數(shù)據(jù)拷貝到直接內(nèi)存腊满,再拷貝到j(luò)vm內(nèi)存中,兩次拷貝

而DirectByteBuffer直接指向直接內(nèi)存培己,省去了一步拷貝工作碳蛋,這種技術(shù)也叫零拷貝,讀取數(shù)據(jù)更快

對比

那問題就來了省咨,既然直接內(nèi)存IO速度都很快肃弟,為啥我們常用的確實HeapByteBuffer?

  • 相比于堆內(nèi)存零蓉,直接內(nèi)存的分配時間較長笤受,因為JVM內(nèi)存是物理內(nèi)存提前分配好的,屬于虛擬機自己的內(nèi)存分配肯定很快敌蜂,而堆外內(nèi)存需要重新向物理內(nèi)存索要額外空間箩兽,肯定需要更長時間

  • 還有一個重要原因:堆外內(nèi)存不受GC管控,容易造成內(nèi)存溢出(可以調(diào)用system.gc手動GC)

ByteBuf

netty中封裝了一個ByteBuf紊册,就使用到了DirectByteBuffer來創(chuàng)建直接內(nèi)存比肄,實現(xiàn)零拷貝,那么上面介紹了使用直接內(nèi)存的缺點netty是如何攻破的吶

內(nèi)存池設(shè)計

針對直接內(nèi)存分配時間長的問題囊陡,netty使用內(nèi)存池設(shè)計,為了盡量重用緩沖區(qū)減少分配時間掀亥,Netty提供了基于ByteBuf內(nèi)存池的緩沖區(qū)重用機制撞反。需要的時候直接從池子里獲取ByteBuf使用即可,使用完畢之后就重新放回到池子里去

至于堆外內(nèi)存不收GC管控問題搪花,畢竟只是一個代碼難寫的問題遏片,只要考慮到了手動回收即可

當(dāng)然除零拷貝之外,ByteBuf還做了一些改進撮竿,使這個字節(jié)緩沖區(qū)更適用于網(wǎng)絡(luò)IO場景

讀寫索引分離

相比如Buffer設(shè)計的position同時標(biāo)志讀寫位置這種用起來很蹩腳的方式吮便,ByteBuf提供了兩個索引:readerIndex 和 writerIndex

index

通過readerindex和writerIndex和capacity,將buffer分成三個區(qū)域

  • 已經(jīng)讀取的區(qū)域:[0,readerindex)
  • 可讀取的區(qū)域:[readerindex,writerIndex)
  • 可寫的區(qū)域: [writerIndex,capacity)
動態(tài)擴容

ByteBuf內(nèi)部有這么幾個屬性

  • minNewCapacity:表用戶需要寫入的目標(biāo)值大小
  • threshold:閾值幢踏,為Bytebuf內(nèi)部設(shè)定容量的最大值髓需,默認4M
  • maxCapacity:Netty最大能接受的容量大小,一般為int的最大值
ByteBuf
擴容方法

1..如果目標(biāo)值等于閾值房蝉,使用閥值作為實際容量值

minNewCapacity=threshold

2.如果目標(biāo)值大于閾值僚匆,用每次步進4MB的方式進行內(nèi)存擴張((需要擴容值/4MB)X4MB), 如果超過maxCapacity直接使用maxCapacity作為實際容量值

minNewCapacity>threshold

3.如果目標(biāo)值小于閥值微渠,采用倍增的方式,以64(字節(jié))作為基本數(shù)值咧擂,每次翻倍增長64 -->128 --> 256逞盆,直到倍增后的結(jié)果大于或等于需要的容量值,倍增的結(jié)果作為實際容量值

minNewCapacity<threshold
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末松申,一起剝皮案震驚了整個濱河市云芦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贸桶,老刑警劉巖焕数,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異刨啸,居然都是意外死亡堡赔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門设联,熙熙樓的掌柜王于貴愁眉苦臉地迎上來善已,“玉大人,你說我怎么就攤上這事离例』煌牛” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵宫蛆,是天一觀的道長艘包。 經(jīng)常有香客問我,道長耀盗,這世上最難降的妖魔是什么想虎? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮叛拷,結(jié)果婚禮上舌厨,老公的妹妹穿的比我還像新娘。我一直安慰自己忿薇,他們只是感情好裙椭,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著署浩,像睡著了一般揉燃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上筋栋,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天炊汤,我揣著相機與錄音,去河邊找鬼。 笑死婿崭,一個胖子當(dāng)著我的面吹牛拨拓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播氓栈,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼渣磷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了授瘦?” 一聲冷哼從身側(cè)響起醋界,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎提完,沒想到半個月后形纺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡徒欣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年逐样,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片打肝。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡脂新,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粗梭,到底是詐尸還是另有隱情争便,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布断医,位于F島的核電站滞乙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鉴嗤。R本人自食惡果不足惜斩启,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躬窜。 院中可真熱鬧浇垦,春花似錦、人聲如沸荣挨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽默垄。三九已至,卻和暖如春甚纲,著一層夾襖步出監(jiān)牢的瞬間口锭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鹃操,地道東北人韭寸。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像荆隘,于是被迫代替她去往敵國和親恩伺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

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

  • Buffer和Channel總是成對出現(xiàn)椰拒,在Java NIO中Buffer用于和NIO通道進行交互晶渠,數(shù)據(jù)總是從Ch...
    zhanglbjames閱讀 610評論 0 0
  • 什么是ByteBuf Netty提供了強大的隨機和順序訪問零字節(jié)或多個字節(jié)的序列,為一個或多個原始的字節(jié)數(shù)組和JD...
    0愛上1閱讀 4,507評論 0 1
  • 在分析 Netty 框架的時候燃观,我們首先介紹 ByteBuf 相關(guān)類褒脯,因為網(wǎng)絡(luò)傳輸最終都是字節(jié)數(shù)據(jù),所以如何管理字...
    wo883721閱讀 707評論 0 0
  • 緩沖區(qū)本質(zhì)是一塊可以寫入數(shù)據(jù)缆毁,然后可以從中讀取數(shù)據(jù)的內(nèi)存番川,這塊內(nèi)存被包裹成NIO buffer對象,并且提供了一些...
    RoyRuan閱讀 1,846評論 0 1
  • Buffer java NIO庫是在jdk1.4中引入的脊框,NIO與IO之間的第一個區(qū)別在于颁督,IO是面向流的,而NI...
    德彪閱讀 2,202評論 0 3