一.Buffer
- Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存(JVM中)。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
- 层次图(以ByteBuffer为例)
/*参数:以写为例*/
//存储当前位置
private int mark = -1;
//写入数据当前的位置
private int position = 0;
//最多能往Buffer里写多少数据,写模式下,limit等于Buffer的capacity
private int limit;
//作为一个内存块,Buffer有一个固定的大小值,通过读数据或者清除数据清除buffer。
private int capacity;
//仅用于直接缓存区,代表内存映射的地址
long address;
- buffer的读写切换flip()解析
- 先看源码,结合上图ReadMode,我们可以发现flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。换句话说,读模式下position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
- buffer的释放:clear(),remaining;标志mark()方法等不再解析,有兴趣可以查看源码
二.ByteBuffer
// 不为空且只供非直接缓存区使用
final byte[] hb;
// 数组偏移量
final int offset;
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
//继承Buffer的构造器
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
- 分配非直接缓存区allocate()
通过源码发现返回了一个HeapByteBuffer对象,这个对象是继承于ByteBuffer
public static ByteBuffer allocate(int capacity) {
......
return new HeapByteBuffer(capacity, capacity);
}
- 我们看看HeapByteBuffer是什么
- 未定义新的变量,构造非直接缓存区(为父类的构造器提供初始值)
- 实现缓存区压缩compact方法:丢弃已经释放的数据,保留还没有释放的数据,并且对缓冲区的重新填充作准备。
- 实现缓冲区存取方法put和get方法,通过下面源码我们发现,final byte[] hb是buffer存储数据的容器
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复制到hb中
System.arraycopy(src, offset, hb, ix(position()), length);
position(position() + length);
return this;
}
public byte get() {
return hb[position++];
}
- 分配直接缓存区allocateDirect
通过源码发现返回了一个DirectByteBuffer对象,这个对象是继承于ByteBuffer
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
- 我们看看DirectByteBuffer是什么
- 堆外内存就是把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。
DirectByteBuffer(int cap) {
//引用父类构造器
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 {
//在堆外内存的基地址,指定内存大小
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
//把新申请的内存数据清零
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;
}
总结
- 缓冲区基础操作:存取(put,get),翻转(flip),释放(:clear,remaining),压缩(compact),标志(mark),批量移动(面向块hb)
- 创建缓存区:allocateDirect,allocate
参考:
从0到1起步-跟我进入堆外内存的奇妙世界
浅析Java Nio 之缓冲区