一.Buffer
- Java NIO中的Buffer用于和NIO通道進(jìn)行交互。如你所知,數(shù)據(jù)是從通道讀入緩沖區(qū)往扔,從緩沖區(qū)寫入到通道中的。
緩沖區(qū)本質(zhì)上是一塊可以寫入數(shù)據(jù)熊户,然后可以從中讀取數(shù)據(jù)的內(nèi)存(JVM中)萍膛。這塊內(nèi)存被包裝成NIO Buffer對(duì)象,并提供了一組方法敏弃,用來(lái)方便的訪問(wèn)該塊內(nèi)存卦羡。
- 層次圖(以ByteBuffer為例)
- buffer的結(jié)構(gòu)(buffer抽象類定義)
/*參數(shù):以寫為例*/
//存儲(chǔ)當(dāng)前位置
private int mark = -1;
//寫入數(shù)據(jù)當(dāng)前的位置
private int position = 0;
//最多能往Buffer里寫多少數(shù)據(jù),寫模式下,limit等于Buffer的capacity
private int limit;
//作為一個(gè)內(nèi)存塊麦到,Buffer有一個(gè)固定的大小值,通過(guò)讀數(shù)據(jù)或者清除數(shù)據(jù)清除buffer绿饵。
private int capacity;
//僅用于直接緩存區(qū),代表內(nèi)存映射的地址
long address;
- buffer的讀寫切換flip()解析
- 先看源碼,結(jié)合上圖ReadMode,我們可以發(fā)現(xiàn)flip方法將Buffer從寫模式切換到讀模式瓶颠。調(diào)用flip()方法會(huì)將position設(shè)回0拟赊,并將limit設(shè)置成之前position的值。換句話說(shuō)粹淋,讀模式下position現(xiàn)在用于標(biāo)記讀的位置吸祟,limit表示之前寫進(jìn)了多少個(gè)byte、char等 桃移。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
- buffer的釋放:clear(),remaining;標(biāo)志mark()方法等不再解析屋匕,有興趣可以查看源碼
二.ByteBuffer
// 不為空且只供非直接緩存區(qū)使用
final byte[] hb;
// 數(shù)組偏移量
final int offset;
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
//繼承Buffer的構(gòu)造器
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
- 分配非直接緩存區(qū)allocate()
通過(guò)源碼發(fā)現(xiàn)返回了一個(gè)HeapByteBuffer對(duì)象,這個(gè)對(duì)象是繼承于ByteBuffer
public static ByteBuffer allocate(int capacity) {
......
return new HeapByteBuffer(capacity, capacity);
}
- 我們看看HeapByteBuffer是什么
- 未定義新的變量借杰,構(gòu)造非直接緩存區(qū)(為父類的構(gòu)造器提供初始值)
- 實(shí)現(xiàn)緩存區(qū)壓縮compact方法:丟棄已經(jīng)釋放的數(shù)據(jù)过吻,保留還沒有釋放的數(shù)據(jù),并且對(duì)緩沖區(qū)的重新填充作準(zhǔn)備蔗衡。
- 實(shí)現(xiàn)緩沖區(qū)存取方法put和get方法,通過(guò)下面源碼我們發(fā)現(xiàn)纤虽,final byte[] hb是buffer存儲(chǔ)數(shù)據(jù)的容器
public ByteBuffer put(int i, byte x) {
hb[ i + offset] = x;
return this;
}
public ByteBuffer put(byte[] src, int offset, int length) {
checkBounds(offset, length, src.length);
if (length > remaining())
throw new BufferOverflowException();
//src復(fù)制到hb中
System.arraycopy(src, offset, hb, ix(position()), length);
position(position() + length);
return this;
}
public byte get() {
return hb[position++];
}
- 分配直接緩存區(qū)allocateDirect
通過(guò)源碼發(fā)現(xiàn)返回了一個(gè)DirectByteBuffer對(duì)象乳绕,這個(gè)對(duì)象是繼承于ByteBuffer
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
- 我們看看DirectByteBuffer是什么
- 堆外內(nèi)存就是把內(nèi)存對(duì)象分配在Java虛擬機(jī)的堆以外的內(nèi)存,這些內(nèi)存直接受操作系統(tǒng)管理(而不是虛擬機(jī))逼纸,這樣做的結(jié)果就是能夠在一定程度上減少垃圾回收對(duì)應(yīng)用程序造成的影響洋措。
DirectByteBuffer(int cap) {
//引用父類構(gòu)造器
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
//在堆外內(nèi)存的基地址,指定內(nèi)存大小
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
//把新申請(qǐng)的內(nèi)存數(shù)據(jù)清零
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
//保存基地址(虛擬地址)
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
總結(jié)
- 緩沖區(qū)基礎(chǔ)操作:存取(put,get),翻轉(zhuǎn)(flip),釋放(:clear,remaining),壓縮(compact),標(biāo)志(mark),批量移動(dòng)(面向塊hb)
- 創(chuàng)建緩存區(qū):allocateDirect杰刽,allocate
參考:
從0到1起步-跟我進(jìn)入堆外內(nèi)存的奇妙世界
淺析Java Nio 之緩沖區(qū)