相關(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)圖如下:
從圖中發(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;
}
- 將limit設(shè)置成當(dāng)前position的坐標(biāo)
- 將position設(shè)置為0
- 取消標(biāo)記
rewind 方法
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
從源碼中發(fā)現(xiàn)漓帚,rewind修改了position和mark,而沒有修改limit。
- 將position設(shè)置為0
- 取消mark標(biāo)記
clear 方法
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
- 將position坐標(biāo)設(shè)置為0
- limit設(shè)置為capacity
- 取消標(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 類分析
從圖中我們可以發(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)造
直接緩沖區(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 方法
- public byte get()
獲取position坐標(biāo)元素藻烤,并將position+1; - public byte get(int i)
獲取指定索引下標(biāo)的元素 - public ByteBuffer get(byte[] dst)
從當(dāng)前position中讀取元素填充到dst數(shù)組中头滔,每填充一個(gè)元素position+1; - public ByteBuffer get(byte[] dst, int offset, int length)
從當(dāng)前position中讀取元素到dst數(shù)組的offset下標(biāo)開始填充length個(gè)元素怖亭。
put 方法
- public ByteBuffer put(byte x)
寫入一個(gè)元素并position+1 - public ByteBuffer put(int i, byte x)
指定的索引寫入一個(gè)元素 - public final ByteBuffer put(byte[] src)
寫入一個(gè)自己數(shù)組,并position+數(shù)組長(zhǎng)度 - public ByteBuffer put(byte[] src, int offset, int length)
從一個(gè)自己數(shù)組的offset開始length個(gè)元素寫入到ByteBuffer中坤检,并把position+length - public ByteBuffer put(ByteBuffer src)
寫入一個(gè)ByteBuffer兴猩,并position加入寫入的元素個(gè)數(shù)
視圖緩沖區(qū)
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)