在進行數據傳輸的時候,每每須要使用到緩衝區,經常使用的緩衝區就是JDK NIO類庫中提供的java.nio.Buffer,實現類以下:java
在使用NIO編程時,最經常使用的是其中的ByteBuffer,本篇分析ByteBuffer內部的源碼實現,順序從父類Buffer入手,瞭解父類中基礎API的實現,再到各個實現子類的實現。編程
Buffer數組
Buffer是存放一種特定的、原始的數據的容器。Buffer是一種特定原始類型元素的線性的有限序列集合,其核心的屬性有capacity、limit、Position。緩存
capacity:Buffer的容量,表示能夠容納的元素數量app
limit:表示第一個不能夠被讀取或者寫入的元素的位置ide
position:表示下一個被讀取或者寫入的位置spa
三者之間的關係以下:0<=position<=limit<=capacity翻譯
Buffer只有一個構造方法:orm
這個構造方式是protected的,也就是說只有在包內能夠調用。構造方法中除了capacity、limit、position外還有一個mark參數,且校驗了mark參數必須小於position。這個參數很是簡單,用於標記position的當前位置,在進行讀取寫入之類的操做以後能夠經過API從新將position重置到標記的位置,對應的API爲:Buffer#mark()\Buffer#reset()xml
Buffer中一個比較重要的API是Buffer#flip
這個方法就是將limit設置到position位置,將position調整到0,將mark設置爲-1。
爲何須要有這麼一個方法調整位置呢?
這個主要和Buffer只有一個position做爲遊標相關,讀寫都是基於position的,因此在寫操做完成以後須要進行讀操做時,須要將limit設置爲position標記有寫到哪兒了,而將position 從新移到0,這樣就能夠讀取到全部的寫入數據。假設若是有兩個遊標分別表示讀取和寫入的位置,是否就能夠不用這個API了呢?
Buffer中的代碼都很是簡單,主要就是自身屬性信息的設置和返回,像返回position、返回limit信息等,展開細看。
ByteBuffer
ByteBuffer是Buffer的一個子類,是字節緩衝區。ByteBuffer在Buffer之上定義了6中操做:
經過當前位置和指定位置的方式讀取和寫入byte
經過get(byte[])的方式將ByteBuffer中的數據讀取到byte[]中
經過put(byte[])的方式將連續大量的byte數據寫入緩衝區
經過當前位置和指定位置的方式將其餘類型的數據寫入緩衝區或從緩衝區讀取數據轉換成特定類型
提供將ByteBuffer轉換成其餘類型的Buffer視圖的方法,例如ByteBuffer#asCharBuffer
提供compact、duplicate、slice來執行一些對ByteBuffer的操做
ByteBuffer的構造方法以下:
提供了兩個構造方法,相對於Buffer增長了一個byte數組和一個offset。byte數組用於存儲數據,offset表示ByteBuffer背後實際用於存儲的byte數據的其實位置。即你可使用一個byte數據,從它的任何一個下標開始存儲數據,而不必定是0。
固然,這兩個方法都是protected的,也就是說實際咱們「不能」經過這兩個方法去構造咱們須要的緩衝區。
那麼當咱們須要使用緩衝區的時候咱們如何去構造一個呢?ByteBuffer提供了兩個API:ByteBuffer#allocateDirect、ByteBuffer#allocate
ByteBuffer#allocateDirect分配一個DirectByteBuffer,即這個緩衝區是使用堆外內存的。
ByteBuffer#allocate在JVM堆上分配一塊內存。
新分配的內存position都是0,limit爲容量,初始內部填充的數據都爲0。
除了經過allocate去建立ByteBuffer,還有一種方式是經過wrap來包裝一個byte數組,這樣就可使用ByteBuffer的API來對byte數據進行操做。
由於byte數據自己在堆內,因此wrap的ByteBuffer也就是HeapByteBuffer。
offset和length將被做爲ByteBuffer初始的position和limit。
allocate和wrap都是建立了「新」的ByteBuffer,這裏新的含義是他們背後都有本身獨立的byte數組用於存儲數據。還有一類API,他們也建立ByteBuffer,可是它只是個視圖,擁有本身的position、limit等屬性,可是存儲的byte數組是共享的:
ByteBuffer#slice:建立一個的ByteBuffer,內容是當前ByteBuffer的一個子序列,共享一個byte數組;兩個ByteBuffer的position、limit、mark是獨立的;新ByteBuffer的起始位置是原ByteBuffer的position位置
ByteBuffer#duplicate:「複製」一個ByteBuffer,共享存儲的byte數據,擁有獨立的capacity、limit、position、mark屬性;若是當前ByteBuffer是DirectByteBuffer,那麼新Buffer也是DirectByteBuffer,若是當前是HeapByteBuffer,那麼新分配的也是HeapByteBuffer
ByteBuffer提供另一類API來將本身轉換成另外一個類型的緩衝區:
ByteBuffer#asXXXBuffer:好比asLongBuffer建立一個新的LongBuffer,底層的存儲仍是共享當前的byte數組,同時擁有本身的position、limit、mark屬性,新Buffer的position爲0,limit和capacity爲原Buffer除8,由於一個long類型佔用8個byte;其餘asXXXBuffer方法都相似
ByteBuffer中還有一類API是提供基於當前位置或者指定位置來讀寫數據的:
byte getByte()
byte getByte(int index)
int getInt()
int getInt(int index)
...
這兩種API的差別是沒有參數的API會從當前position開始讀取數據,以後會修改position位置。而經過傳入index,會從index開始讀取數據,不會變動position信息。因此若是隻是要讀取數據,並不但願更改Buffer自己的信息(position),應該使用帶有參數的方法。
ByteBuffer的內容只有這麼多,接着看它的子類實現,主要是HeapByteBuffer和DirectByteBuffer。
HeapByteBuffer
HeapByteBuffer顧名思義就是JVM堆上的字節緩衝區,他用於緩存數據的byte數組就是直接在堆內申請的。默認的構造方法直接就是new一個byte數組做爲數據存儲的緩衝區。
HeapByteBuffer很是簡單,就是實現了ByteBuffer定義的各類put和get方法,沒有什麼好分析的。
DirectByteBuffer
DirectByteBuffer翻譯過來就是直接的字節緩衝區,它是使用直接內存的,即不從JVM的堆上分配內存。
首先看DirectByteBuffer的一個內部類:Deallocator。從類名能夠看出這個類應該是作「回收的」。
從代碼看,Deallocator實現了Runnable接口,run方法內的實現就是經過unsafe釋放內存。
結合Cleaner就能明白Cleaner是統一的接口,返回Cleaner來執行清楚操做,而真正的內存回收在Deallocator中執行。
接着看DirectByteBuffer的構造方法:
只有一個容量做爲參數,而內存是直接經過unsafe分配的,可見內存是直接分配的,而不是在堆上申請的。另外這是一個受保護的方法,也就是說用戶是不能直接調用的。
另外還有幾個構造方法,能夠直接經過內存地址來初始化,或者經過文件描述符來初始化(For memory-mapped buffers),經過已近存在的DirectBuffer來初始化。
這些方法都是提供給MMAP之類的使用的,通常用戶都不會直接調用到。
剩下的方法,像是slice、duplicate,包括經過address返回內存地址都很是簡單就不描述了。
另外DirectByteBuffer內部還有一個特殊的方法是asReadOnlyBuffer方法,返回了一個DirectByteBufferR對象。下面看一下DirectByteBufferR作了些什麼。
簡單從方法出發,大概就是返回只讀的一個對象,不能作寫入操做。
實際上也是很是簡單,全部的put操做都拋出了異常。剩下get和slice等也相似,再也不贅述。
-------------------------------------------------------------------------------------