溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

ByteBuffer源碼分析

發(fā)布時間:2020-07-07 13:36:44 來源:網(wǎng)絡(luò) 閱讀:204 作者:sshpp 欄目:開發(fā)技術(shù)

在進(jìn)行數(shù)據(jù)傳輸?shù)臅r候,往往需要使用到緩沖區(qū),常用的緩沖區(qū)就是JDK NIO類庫中提供的java.nio.Buffer,實(shí)現(xiàn)類如下:

ByteBuffer源碼分析


在使用NIO編程時,最常用的是其中的ByteBuffer,本篇分析ByteBuffer內(nèi)部的源碼實(shí)現(xiàn),順序從父類Buffer入手,了解父類中基礎(chǔ)API的實(shí)現(xiàn),再到各個實(shí)現(xiàn)子類的實(shí)現(xiàn)。

Buffer

Buffer是存放一種特定的、原始的數(shù)據(jù)的容器。Buffer是一種特定原始類型元素的線性的有限序列集合,其核心的屬性有capacity、limit、Position。

 ByteBuffer源碼分析


capacity:Buffer的容量,表示可以容納的元素?cái)?shù)量

limit:表示第一個不可以被讀取或者寫入的元素的位置

position:表示下一個被讀取或者寫入的位置

三者之間的關(guān)系如下:0<=position<=limit<=capacity

Buffer只有一個構(gòu)造方法:

ByteBuffer源碼分析


這個構(gòu)造方式是protected的,也就是說只有在包內(nèi)可以調(diào)用。構(gòu)造方法中除了capacity、limit、position外還有一個mark參數(shù),且校驗(yàn)了mark參數(shù)必須小于position。這個參數(shù)非常簡單,用于標(biāo)記position的當(dāng)前位置,在進(jìn)行讀取寫入之類的操作之后可以通過API重新將position重置到標(biāo)記的位置,對應(yīng)的API為:Buffer#mark()\Buffer#reset()

Buffer中一個比較重要的API是Buffer#flip

ByteBuffer源碼分析


這個方法就是將limit設(shè)置到position位置,將position調(diào)整到0,將mark設(shè)置為-1。

為什么需要有這么一個方法調(diào)整位置呢?

這個主要和Buffer只有一個position作為游標(biāo)相關(guān),讀寫都是基于position的,所以在寫操作完成之后需要進(jìn)行讀操作時,需要將limit設(shè)置為position標(biāo)記有寫到哪兒了,而將position 重新移到0,這樣就可以讀取到所有的寫入數(shù)據(jù)。假設(shè)如果有兩個游標(biāo)分別表示讀取和寫入的位置,是否就可以不用這個API了呢?

 

Buffer中的代碼都非常簡單,主要就是自身屬性信息的設(shè)置和返回,像返回position、返回limit信息等,展開細(xì)看。

ByteBuffer源碼分析


ByteBuffer

ByteBuffer是Buffer的一個子類,是字節(jié)緩沖區(qū)。ByteBuffer在Buffer之上定義了6中操作:

  1. 通過當(dāng)前位置和指定位置的方式讀取和寫入byte

  2. 通過get(byte[])的方式將ByteBuffer中的數(shù)據(jù)讀取到byte[]中

  3. 通過put(byte[])的方式將連續(xù)大量的byte數(shù)據(jù)寫入緩沖區(qū)

  4. 通過當(dāng)前位置和指定位置的方式將其他類型的數(shù)據(jù)寫入緩沖區(qū)或從緩沖區(qū)讀取數(shù)據(jù)轉(zhuǎn)換成特定類型

  5. 提供將ByteBuffer轉(zhuǎn)換成其他類型的Buffer視圖的方法,例如ByteBuffer#asCharBuffer

  6. 提供compact、duplicate、slice來執(zhí)行一些對ByteBuffer的操作

 

ByteBuffer的構(gòu)造方法如下:

ByteBuffer源碼分析


提供了兩個構(gòu)造方法,相對于Buffer增加了一個byte數(shù)組和一個offset。byte數(shù)組用于存儲數(shù)據(jù),offset表示ByteBuffer背后實(shí)際用于存儲的byte數(shù)據(jù)的其實(shí)位置。即你可以使用一個byte數(shù)據(jù),從它的任何一個下標(biāo)開始存儲數(shù)據(jù),而不一定是0。

當(dāng)然,這兩個方法都是protected的,也就是說實(shí)際我們“不能”通過這兩個方法去構(gòu)造我們需要的緩沖區(qū)。

 

那么當(dāng)我們需要使用緩沖區(qū)的時候我們?nèi)绾稳?gòu)造一個呢?ByteBuffer提供了兩個API:ByteBuffer#allocateDirect、ByteBuffer#allocate




ByteBuffer源碼分析

ByteBuffer#allocateDirect分配一個DirectByteBuffer,即這個緩沖區(qū)是使用堆外內(nèi)存的。

ByteBuffer源碼分析


ByteBuffer#allocate在JVM堆上分配一塊內(nèi)存。

新分配的內(nèi)存position都是0,limit為容量,初始內(nèi)部填充的數(shù)據(jù)都為0

 

除了通過allocate去創(chuàng)建ByteBuffer,還有一種方式是通過wrap來包裝一個byte數(shù)組,這樣就可以使用ByteBuffer的API來對byte數(shù)據(jù)進(jìn)行操作。


 ByteBuffer源碼分析

ByteBuffer源碼分析


因?yàn)?span lang="en-us" xml:lang="en-us">byte數(shù)據(jù)本身在堆內(nèi)所以wrapByteBuffer也就是HeapByteBuffer。

offset和length將被作為ByteBuffer初始的position和limit。



allocate和wrap都是創(chuàng)建了“新”的ByteBuffer,這里新的含義是他們背后都有自己獨(dú)立的byte數(shù)組用于存儲數(shù)據(jù)。還有一類API,他們也創(chuàng)建ByteBuffer,但是它只是個視圖,擁有自己的position、limit等屬性,但是存儲的byte數(shù)組是共享的:

  • ByteBuffer#slice:創(chuàng)建一個的ByteBuffer,內(nèi)容是當(dāng)前ByteBuffer的一個子序列,共享一個byte數(shù)組;兩個ByteBuffer的position、limit、mark是獨(dú)立的;新ByteBuffer的起始位置是原ByteBuffer的position位置

  • ByteBuffer#duplicate:“復(fù)制”一個ByteBuffer,共享存儲的byte數(shù)據(jù),擁有獨(dú)立的capacity、limit、position、mark屬性;如果當(dāng)前ByteBuffer是DirectByteBuffer,那么新Buffer也是DirectByteBuffer,如果當(dāng)前是HeapByteBuffer,那么新分配的也是HeapByteBuffer

 

ByteBuffer提供另外一類API來將自己轉(zhuǎn)換成另一個類型的緩沖區(qū):

  • ByteBuffer#asXXXBuffer:比如asLongBuffer創(chuàng)建一個新的LongBuffer,底層的存儲還是共享當(dāng)前的byte數(shù)組,同時擁有自己的position、limit、mark屬性,新Buffer的position為0,limit和capacity為原Buffer除8,因?yàn)橐粋€long類型占用8個byte;其他asXXXBuffer方法都類似

 

ByteBuffer中還有一類API是提供基于當(dāng)前位置或者指定位置來讀寫數(shù)據(jù)的:

  • byte getByte()

  • byte getByte(int index)

  • int getInt()

  • int getInt(int index)

  • ...

這兩種API的差異是沒有參數(shù)的API會從當(dāng)前position開始讀取數(shù)據(jù),之后會修改position位置。而通過傳入index,會從index開始讀取數(shù)據(jù),不會變更position信息。所以如果只是要讀取數(shù)據(jù),并不希望更改Buffer本身的信息(position),應(yīng)該使用帶有參數(shù)的方法。

 

ByteBuffer的內(nèi)容只有這么多,接著看它的子類實(shí)現(xiàn),主要是HeapByteBuffer和DirectByteBuffer。


HeapByteBuffer

HeapByteBuffer顧名思義就是JVM堆上的字節(jié)緩沖區(qū),他用于緩存數(shù)據(jù)的byte數(shù)組就是直接在堆內(nèi)申請的。默認(rèn)的構(gòu)造方法直接就是new一個byte數(shù)組作為數(shù)據(jù)存儲的緩沖區(qū)。

 ByteBuffer源碼分析

HeapByteBuffer非常簡單,就是實(shí)現(xiàn)了ByteBuffer定義的各種put和get方法,沒有什么好分析的。


DirectByteBuffer

DirectByteBuffer翻譯過來就是直接的字節(jié)緩沖區(qū),它是使用直接內(nèi)存的,即不從JVM的堆上分配內(nèi)存。

首先看DirectByteBuffer的一個內(nèi)部類:Deallocator。從類名可以看出這個類應(yīng)該是做“回收的”。


ByteBuffer源碼分析

從代碼看,Deallocator實(shí)現(xiàn)了Runnable接口,run方法內(nèi)的實(shí)現(xiàn)就是通過unsafe釋放內(nèi)存。

ByteBuffer源碼分析

ByteBuffer源碼分析

結(jié)合Cleaner就能明白Cleaner是統(tǒng)一的接口,返回Cleaner來執(zhí)行清楚操作,而真正的內(nèi)存回收在Deallocator中執(zhí)行。

接著看DirectByteBuffer的構(gòu)造方法:

ByteBuffer源碼分析


只有一個容量作為參數(shù),而內(nèi)存是直接通過unsafe分配的,可見內(nèi)存是直接分配的,而不是在堆上申請的。另外這是一個受保護(hù)的方法,也就是說用戶是不能直接調(diào)用的。

另外還有幾個構(gòu)造方法,可以直接通過內(nèi)存地址來初始化,或者通過文件描述符來初始化(For memory-mapped buffers),通過已近存在的DirectBuffer來初始化。

ByteBuffer源碼分析

ByteBuffer源碼分析

ByteBuffer源碼分析


這些方法都是提供給MMAP之類的使用的,一般用戶都不會直接調(diào)用到。

剩下的方法,像是slice、duplicate,包括通過address返回內(nèi)存地址都非常簡單就不描述了。

 

另外DirectByteBuffer內(nèi)部還有一個特殊的方法是asReadOnlyBuffer方法,返回了一個DirectByteBufferR對象。下面看一下DirectByteBufferR做了些什么。

 

簡單從方法出發(fā),大概就是返回只讀的一個對象,不能做寫入操作。



ByteBuffer源碼分析

實(shí)際上也是非常簡單,所有的put操作都拋出了異常。剩下get和slice等也類似,不再贅述。


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI