NIO 之 ByteBuffer实现原理
相關文章
IO、NIO、AIO 內部原理分析
NIO 之 Selector實現原理
NIO 之 Channel實現原理
前言
Java NIO 主要由下面3部分組成:
- Buffer
- Channel
- Selector
在傳統IO中,流是基于字節的方式進行讀寫的。
在NIO中,使用通道(Channel)基于緩沖區數據塊的讀寫。
流是基于字節一個一個的讀取和寫入。
通道是基于塊的方式進行讀取和寫入。
Buffer 類結構圖
Buffer 的類結構圖如下:
從圖中發現java中8中基本的類型,除了boolean外,其它的都有特定的Buffer子類。
Buffer類分析
Filed
每個緩沖區都有這4個屬性,無論緩沖區是何種類型都有相同的方法來設置這些值
private int mark = -1; private int position = 0; private int limit; private int capacity;1. 標記(mark)
初始值-1,表示未標記。
標記一個位置,方便以后reset重新從該位置讀取數據。
2. 位置(position)
緩沖區中讀取或寫入的下一個位置。這個位置從0開始,最大值等于緩沖區的大小
//獲取緩沖區的位置 public final int position() {return position; } //設置緩沖區的位置 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; } //設置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)
緩沖區可以保存元素的最大數量。該值在創建緩存區時指定,一旦創建完成后就不能修改該值。
//獲取緩沖區的容量 public final int capacity() {return capacity; }filp 方法
public final Buffer flip() {limit = position;position = 0;mark = -1;return this; }rewind 方法
public final Buffer rewind() {position = 0;mark = -1;return this; }從源碼中發現,rewind修改了position和mark,而沒有修改limit。
1. 將position設置為0
2. 取消mark標記
clear 方法
public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}從clear方法中,我們發現Buffer中的數據沒有清空,如果通過Buffer.get(i)的方式還是可以訪問到數據的。如果再次向緩沖區中寫入數據,他會覆蓋之前存在的數據。
remaining 方法
查看當前位置和limit之間的元素數。
public final int remaining() {return limit - position; }hasRemaining 方法
判斷當前位置和limit之間是否還有元素
public final boolean hasRemaining() {return position < limit; }ByteBuffer 類分析
從圖中我們可以發現 ByteBuffer繼承于Buffer類,ByteBuffer是個抽象類,它有兩個實現的子類HeapByteBuffer和MappedByteBuffer類
HeapByteBuffer:在堆中創建的緩沖區。就是在jvm中創建的緩沖區。
MappedByteBuffer:直接緩沖區。物理內存中創建緩沖區,而不在堆中創建。
allocate 方法(創建堆緩沖區)
public static ByteBuffer allocate(int capacity) {if (capacity < 0)throw new IllegalArgumentException();return new HeapByteBuffer(capacity, capacity); }我們發現allocate方法創建的緩沖區是創建的HeapByteBuffer實例。
HeapByteBuffer 構造
HeapByteBuffer(int cap, int lim) { // package-privatesuper(-1, 0, lim, cap, new byte[cap], 0); }從堆緩沖區中看出,所謂堆緩沖區就是在堆內存中創建一個byte[]數組。
allocateDirect創建直接緩沖區
public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity); }我們發現allocate方法創建的緩沖區是創建的DirectByteBuffer實例。
DirectByteBuffer構造
直接緩沖區是通過java中Unsafe類進行在物理內存中創建緩沖區。
wrap 方法
public static ByteBuffer wrap(byte[] array) public static ByteBuffer wrap(byte[] array, int offset, int length);可以通過wrap類把字節數組包裝成緩沖區ByteBuffer實例。
這里需要注意的的,把array的引用賦值給ByteBuffer對象中字節數組。如果array數組中的值更改,則ByteBuffer中的數據也會更改的。
get 方法
獲取position坐標元素,并將position+1;
獲取指定索引下標的元素
從當前position中讀取元素填充到dst數組中,每填充一個元素position+1;
從當前position中讀取元素到dst數組的offset下標開始填充length個元素。
put 方法
寫入一個元素并position+1
指定的索引寫入一個元素
寫入一個自己數組,并position+數組長度
從一個自己數組的offset開始length個元素寫入到ByteBuffer中,并把position+length
寫入一個ByteBuffer,并position加入寫入的元素個數
視圖緩沖區
ByteBuffer可以轉換成其它類型的Buffer。例如CharBuffer、IntBuffer 等。
壓縮緩沖區
public ByteBuffer compact() {System.arraycopy(hb, ix(position()), hb, ix(0), remaining());position(remaining());limit(capacity());discardMark();return this;}1、把緩沖區positoin到limit中的元素向前移動positoin位
2、設置position為remaining()
3、 limit為緩沖區容量
4、取消標記
例如:ByteBuffer.allowcate(10);
內容:[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); }創建一個分片緩沖區。分配緩沖區與主緩沖區共享數據。
分配的起始位置是主緩沖區的position位置
容量為limit-position。
分片緩沖區無法看到主緩沖區positoin之前的元素。
直接緩沖區和堆緩沖區性能對比
下面我們從緩沖區創建的性能和讀取性能兩個方面進行性能對比。
讀寫性能對比
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("堆緩沖區讀寫耗時 :"+(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("直接緩沖區讀寫耗時:"+(System.currentTimeMillis()-start)); }輸出結果:
堆緩沖區創建耗時 :70 直接緩沖區創建耗時:47從結果中我們發現堆緩沖區讀寫比直接緩沖區讀寫耗時更長。
#
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("堆緩沖區創建時間:"+(System.currentTimeMillis()-start));start = System.currentTimeMillis();for (int i = 0; i < time; i++) {ByteBuffer buffer = ByteBuffer.allocateDirect(4);}System.out.println("直接緩沖區創建時間:"+(System.currentTimeMillis()-start)); }輸出結果:
堆緩沖區創建時間:73 直接緩沖區創建時間:5146從結果中發現直接緩沖區創建分配空間比較耗時。
對比結論
直接緩沖區比較適合讀寫操作,最好能重復使用直接緩沖區并多次讀寫的操作。
堆緩沖區比較適合創建新的緩沖區,并且重復讀寫不會太多的應用。
建議:如果經過性能測試,發現直接緩沖區確實比堆緩沖區效率高才使用直接緩沖區,否則不建議使用直接緩沖區。
本人簡書blog地址:http://www.jianshu.com/u/1f0067e24ff8????
點擊這里快速進入簡書
GIT地址:http://git.oschina.net/brucekankan/
點擊這里快速進入GIT
總結
以上是生活随笔為你收集整理的NIO 之 ByteBuffer实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高亮标红
- 下一篇: NIO 之 Channel实现原理