前言:
在Buffer这篇博客中,笔者在测试ByteBuffer API时,一直在使用HeapByteBuffer在测试。实际ByteBuffer作为一个抽象类,还有其他实现类。如下图所示:

本文中会重点介绍其四种实现类,HeapByteBuffer、HeapByteBufferR、DirectByteBuffer、DirectByteBufferR。
而关于MappedByteBuffer,后续会单独列一篇博客来介绍。
1. HeapByteBuffer
Heap代表堆空间,顾名思义,这个Buffer是分配在JVM堆上的,该区域受JVM管理,回收也由GC来负责。
通过查看其源码可以看到其分配操作过程
2.HeapByteBufferR
class HeapByteBuffer
extends ByteBuffer
{
// 构造方法
protected HeapByteBuffer(byte[] buf,
int mark, int pos, int lim, int cap,
int off)
{
super(mark, pos, lim, cap, buf, off);
}
}
// ByteBuffer
public abstract class ByteBuffer
extends Buffer
implements Comparable
{
final byte[] hb; // Non-null only for heap buffers
final int offset;
boolean isReadOnly; // Valid only for heap buffers
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
}
通过源码可以很清楚的看到,HeapByteBuffer本质上是一个字节数组,通过position、limit的控制,来反复的操作该字节数组。
那么该类与HeapByteBuffer有什么区别呢?直接看源码
class HeapByteBufferR
extends HeapByteBuffer
{
protected HeapByteBufferR(byte[] buf,
int mark, int pos, int lim, int cap,
int off)
{
// 直接调用HeapByteBuffer的赋值方法
super(buf, mark, pos, lim, cap, off);
// 设置为只读
this.isReadOnly = true;
}
}
可以看到,该类与HeapByteBuffer几乎没有区别,除了属性isReadOnly之外,该属性是属于ByteBuffer的属性。
那么设置isReadOnly=true,将HeapByteBufferR设置为只读后,具体有哪些限制呢?继续看源码
public ByteBuffer put(byte x) {
throw new ReadOnlyBufferException();
}
public ByteBuffer put(int i, byte x) {
throw new ReadOnlyBufferException();
}
...
所有的put方法都抛出了异常,不允许对数组中的值进行添加操作了。
那么问题来了,既然不允许以put方式来对HeapByteBufferR进行赋值操作,那要怎样才能赋值呢,看下面的示例
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("risk".getBytes());
// 直接对原HeapByteBuffer进行操作,会生成一个与原HeapByteBuffer一样的buffer,且只读
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
总结:每一个类型的ByteBuffer都有只读和非只读类型的实现类,只读实现类默认以R结尾。
3. DirectByteBuffer
字面意思是直接缓冲区。何为直接缓冲呢?
实际就是堆外内存,该内存块不属于JVM的Heap堆,而是操作系统的其他内存块(本质上就是C语言用malloc进行分配所得到的内存块)。
通过源码我们可以看到其与HeapByteBuffer分配时的不同
// ByteBuffer
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
// DirectByteBuffer
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 {
// 通过unsafe来分配内存
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来获取内存地址引用
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
// Unsafe
public native long allocateMemory(long var1);
Unsafe.allocateMemory是一个native方法,会调用到本操作系统相关的实现方法。(也就是上述所说的,C语言直接调用malloc创建的内存块)。而关于其put get等方法,都是通过Unsafe来控制的
// DirectByteBuffer
public ByteBuffer put(byte x) {
unsafe.putByte(ix(nextPutIndex()), ((x)));
return this;
}
public byte get() {
return ((unsafe.getByte(ix(nextGetIndex()))));
}
4.DirectByteBuffer与HeapByteBuffer异同
Q:既然我们已经有了HeapByteBuffer,那为什么还需要DirectByteBuffer呢?
A:是由于操作系统没法直接访问JVM内存。
细细想来,这个答案有明显的不合理处,操作系统作为底层设施,所有的进程都运行在其上,内存都由其来分配,怎么可能无法操作JVM的内存呢?
理论上来说,操作系统是可以访问JVM内存空间的,但是由于JVM需要进行GC,如果当IO设备直接和JVM堆内存数据直接交互,此时JVM进行了GC操作,原来IO设备操作的字节被移动到其他区域,那IO设备便无法正确的获取到该字节数据。
而DirectByteBuffer,是由操作系统直接分配的,位置不会变动,是可以与IO设置直接进行交互的。所以实际上当IO设备与HeapByteBuffer进行交互时,会先将HeapByteBuffer中的数据临时拷贝到DirectByteBuffer(临时创建的,使用后销毁),然后再从DirectByteBuffer拷贝到IO设置内存空间(一般就是内核空间)
1)源码释疑我们可以通过源码来验证上面这段话的正确性(会涉及到后面Channel的知识点)
// 我们通过FileChannel打开一个文件,然后将HeapByteBuffer中的数据写入到该文件中
RandomAccessFile file = new RandomAccessFile(new File("C:\\Users\\lucky\\Desktop\\filetest.txt"), "rwd");
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte) 'h').put((byte) 'e').put((byte) 'l').put((byte) 'l').put((byte) 'o');
buffer.position(0);
// 通过channel将buffer中的数据写入
channel.write(buffer);
观察FileChannel.write
// FileChannelImpl
public int write(ByteBuffer var1) throws IOException {
this.ensureOpen();
if (!this.writable) {
throw new NonWritableChannelException();
} else {
synchronized(this.positionLock) {
int var3 = 0;
int var4 = -1;
try {
this.begin();
var4 = this.threads.add();
if (!this.isOpen()) {
byte var12 = 0;
return var12;
} else {
do {
// 通过IOUtil来写入
var3 = IOUtil.write(this.fd, var1, -1L, this.nd);
} while(var3 == -3 && this.isOpen());
int var5 = IOStatus.normalize(var3);
return var5;
}
} finally {
this.threads.remove(var4);
this.end(var3 > 0);
assert IOStatus.check(var3);
}
}
}
}
// IOUtil.java
static int write(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
if (var1 instanceof DirectBuffer) {
// 如果使用的就是DirectByteBuffer,则直接写入
return writeFromNativeBuffer(var0, var1, var2, var4);
} else {
int var5 = var1.position();
int var6 = var1.limit();
assert var5
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?
立即登录/注册


微信扫码登录