本篇文章我们讲解缓存区 ByteBuf
八大主要类型中两种,未池化直接缓冲区 UnpooledDirectByteBuf
和 未池化不安全直接缓冲区 UnpooledUnsafeDirectByteBuf
。
一. UnpooledDirectByteBuf
1.1 介绍
A NIO ByteBuffer based buffer.
It is recommended to use
UnpooledByteBufAllocator.directBuffer(int, int),
Unpooled.directBuffer(int) and
Unpooled.wrappedBuffer(ByteBuffer)
instead of calling the constructor explicitly.
UnpooledDirectByteBuf
一个基于NIO ByteBuffer
的缓冲区。
建议使用UnpooledByteBufAllocator.directBuffer(int, int)
,Unpooled.directBuffer(int)
和Unpooled.wrappedBuffer(ByteBuffer)
;而不是显式调用构造函数。
1.2 成员属性
private final ByteBufAllocator alloc;
// accessed by UnpooledUnsafeNoCleanerDirectByteBuf.reallocateDirect()
ByteBuffer buffer;
private ByteBuffer tmpNioBuf;
private int capacity;
private boolean doNotFree;
有四个成员属性:
-
alloc
: 创建此缓存区的ByteBufAllocator
对象。 -
buffer
:NIO
缓存区,用来储存此缓存区的内容数据。
3.tmpNioBuf
: 临时的NIO
缓存区ByteBuffer
对象,其实它是buffer
的一个duplicate
对象。 -
capacity
: 此缓存区的当前容量。 -
doNotFree
: 是否不用释放资源。如果这个值为true
,那么最后此缓存区不用释放持有的NIO
缓存区buffer
资源。
1.3 构造方法
/**
* 创建一个新的直接缓冲区。
*/
public UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
super(maxCapacity);
ObjectUtil.checkNotNull(alloc, "alloc");
checkPositiveOrZero(initialCapacity, "initialCapacity");
checkPositiveOrZero(maxCapacity, "maxCapacity");
if (initialCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format(
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
}
this.alloc = alloc;
setByteBuffer(allocateDirect(initialCapacity), false);
}
通过 allocateDirect(initialCapacity)
方法创建一个新的NIO
缓存区实例来初始化此缓存区对象。
/**
* 通过包装指定的初始缓冲区来创建一个新的直接缓冲区。
*/
protected UnpooledDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer, int maxCapacity) {
this(alloc, initialBuffer, maxCapacity, false, true);
}
UnpooledDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer,
int maxCapacity, boolean doFree, boolean slice) {
super(maxCapacity);
ObjectUtil.checkNotNull(alloc, "alloc");
ObjectUtil.checkNotNull(initialBuffer, "initialBuffer");
if (!initialBuffer.isDirect()) {
throw new IllegalArgumentException("initialBuffer is not a direct buffer.");
}
if (initialBuffer.isReadOnly()) {
throw new IllegalArgumentException("initialBuffer is a read-only buffer.");
}
int initialCapacity = initialBuffer.remaining();
if (initialCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format(
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
}
this.alloc = alloc;
doNotFree = !doFree;
setByteBuffer((slice ? initialBuffer.slice() : initialBuffer).order(ByteOrder.BIG_ENDIAN), false);
writerIndex(initialCapacity);
}
利用现有的NIO
缓存区创建此缓存区。
1.4 重要方法
1.4.1 创建,释放和替换 NIO
缓存区
- 创建
NIO
缓存区
通过protected ByteBuffer allocateDirect(int initialCapacity) { return ByteBuffer.allocateDirect(initialCapacity); }
ByteBuffer
的allocateDirect
方法,创建一个NIO
缓存区DirectByteBuffer
对象。 - 释放
NIO
缓存区
调用protected void freeDirect(ByteBuffer buffer) { PlatformDependent.freeDirectBuffer(buffer); }
PlatformDependent
的方法来释放DirectByteBuffer
对象。 - 替换
NIO
缓存区void setByteBuffer(ByteBuffer buffer, boolean tryFree) { // 是否需要释放老的 NIO 缓存区 if (tryFree) { ByteBuffer oldBuffer = this.buffer; if (oldBuffer != null) { if (doNotFree) { doNotFree = false; } else { // 释放老的 NIO 缓存区 freeDirect(oldBuffer); } } } // 替换此缓存区拥有的 buffer this.buffer = buffer; tmpNioBuf = null; // 得到当前缓存区容量 capacity = buffer.remaining(); }
1.4.2 设置缓存区容量
@Override
public int capacity() {
return capacity;
}
// 在容量减少后调用
protected final void trimIndicesToCapacity(int newCapacity) {
// 写索引比 新容量大,那么才需要改变读写索引的值
if (writerIndex() > newCapacity) {
// 写索引的值就是新容量newCapacity,
// 而读索引的值是读索引和新容量newCapacity之间的较小值
setIndex0(Math.min(readerIndex(), newCapacity), newCapacity);
}
}
@Override
public ByteBuf capacity(int newCapacity) {
// 检查新容量是否越界
checkNewCapacity(newCapacity);
// 老的缓存区容量
int oldCapacity = capacity;
// 新老容量相等,那么不用改变,直接返回
if (newCapacity == oldCapacity) {
return this;
}
// 需要复制的内容字节数
int bytesToCopy;
if (newCapacity > oldCapacity) {
// 新容量比老容量大,即扩大缓存区,
// 那么复制的内容字节数就是老容量大小
bytesToCopy = oldCapacity;
} else {
// 新容量比老容量小,就要缩小缓存区
// 那么读写索引也要改变。
trimIndicesToCapacity(newCapacity);
// 需要复制的内容字节数也就是新容量大小
bytesToCopy = newCapacity;
}
ByteBuffer oldBuffer = buffer;
// 创建新容量的 NIO 缓存区对象
ByteBuffer newBuffer = allocateDirect(newCapacity);
oldBuffer.position(0).limit(bytesToCopy);
newBuffer.position(0).limit(bytesToCopy);
// 将老缓冲区数据存入新NIO缓存区newBuffer中
newBuffer.put(oldBuffer).clear();
// 替换新NIO缓存区newBuffer
setByteBuffer(newBuffer, true);
return this;
}
你会发现当更改缓存区容量时,分为两种情况:
- 当新容量比老容量大的时候,即扩大缓存区,这时比较简单,只需要将老的缓存区字节数组数据复制到新的缓存区字节数组数据中就可以了。
- 当新容量比老容量小的时候,即缩小缓存区,这个时候除了数据内容的复制,可能还需要更改读写索引。
1.4.3 get
系列方法
1.4.3.1 获取基本数据类型
@Override
public short getShort(int index) {
ensureAccessible();
return _getShort(index);
}
@Override
protected short _getShort(int index) {
return buffer.getShort(index);
}
@Override
protected short _getShortLE(int index) {
return ByteBufUtil.swapShort(buffer.getShort(index));
}
......
通过 NIO
缓存区buffer
对应方法获取基本数据类型数据。
1.4.3.2 getBytes(int index, ByteBuf dst, int dstIndex, int length)
@Override
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
// 检查是否越界
checkDstIndex(index, length, dstIndex, dst.capacity());
if (dst.hasArray()) {
// 如果目标缓存区是堆缓存区,那么目标缓存区就有字节数组 dst.array()
// 调用 getBytes(int, byte[], int, int) 方法,进行字节数组之间的复制
getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
} else if (dst.nioBufferCount() > 0) {
// 如果目标缓存区dst能转成 NIO 缓存区,得到目标缓存区dst对应的 NIO缓存区数组
// 遍历 NIO缓存区数组, 调用 getBytes(int, ByteBuffer) 方法,
// 将次缓存区的数据写入到目标缓存区。
for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) {
int bbLen = bb.remaining();
getBytes(index, bb);
index += bbLen;
}
} else {
// 以上情况都不符合,调用目标缓存区dst 的 setBytes(int, ByteBuf, int, int)
// 将此缓存区数据传输到目标缓存区dst 中。
dst.setBytes(dstIndex, this, index, length);
}
return this;
}
根据目标缓存区dst
类型不同,处理的方式也不同。
1.4.3.3 getBytes(int index, byte[] dst, int dstIndex, int length)
@Override
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
getBytes(index, dst, dstIndex, length, false);
return this;
}
void getBytes(int index, byte[] dst, int dstIndex, int length, boolean internal) {
checkDstIndex(index, length, dstIndex, dst.length);
// 得到此缓存区对应的 NIO缓存区对象tmpBuf
ByteBuffer tmpBuf;
if (internal) {
tmpBuf = internalNioBuffer();
} else {
tmpBuf = buffer.duplicate();
}
tmpBuf.clear().position(index).limit(index + length);
// 通过 ByteBuffer 的get方法,将NIO缓存区tmpBuf的数据读取到 目标字节数组dst
tmpBuf.get(dst, dstIndex, length);
}
1.4.3.4 getBytes(int index, ByteBuffer dst)
@Override
public ByteBuf getBytes(int index, ByteBuffer dst) {
getBytes(index, dst, false);
return this;
}
void getBytes(int index, ByteBuffer dst, boolean internal) {
checkIndex(index, dst.remaining());
// 得到此缓存区对应的 NIO缓存区对象tmpBuf
ByteBuffer tmpBuf;
if (internal) {
tmpBuf = internalNioBuffer();
} else {
tmpBuf = buffer.duplicate();
}
tmpBuf.clear().position(index).limit(index + dst.remaining());
// 调用目标缓存区 dst 的 put 方法,将此缓存区的数据写入到目标缓存区 dst 中。
dst.put(tmpBuf);
}
1.4.3.5 和 IO
流的交互
你会发现这些方法都是获取此缓存区对应 NIO
缓存区ByteBuffer
对象,调用 ByteBuffer
对象的方法,与 IO
流的交互,进行数据传输
@Override
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
return getBytes(index, out, length, false);
}
private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
ensureAccessible();
if (length == 0) {
return 0;
}
ByteBuffer tmpBuf;
if (internal) {
tmpBuf = internalNioBuffer();
} else {
tmpBuf = buffer.duplicate();
}
tmpBuf.clear().position(index).limit(index + length);
return out.write(tmpBuf);
}
@Override
public int getBytes(int index, FileChannel out, long position, int length) throws IOException {
return getBytes(index, out, position, length, false);
}
private int getBytes(int index, FileChannel out, long position, int length, boolean internal) throws IOException {
ensureAccessible();
if (length == 0) {
return 0;
}
ByteBuffer tmpBuf = internal ? internalNioBuffer() : buffer.duplicate();
tmpBuf.clear().position(index).limit(index + length);
return out.write(tmpBuf, position);
}
1.4.4 set
系列方法
和 get
系列方法一样,set
系列的实现也是靠 NIO
缓存区ByteBuffer
对应方法。
1.4.5 剩余方法
剩余方法也几乎都是和 NIO
缓存区ByteBuffer
有关,而且也不难,就不做过多介绍了。
1.5 小结
UnpooledDirectByteBuf
主要是通过 NIO
缓存区 buffer
来存储数据。而它获取和设置数据,也都是通过 NIO
缓存区对应方法实现的。
二. UnpooledUnsafeDirectByteBuf
2.1 介绍
A NIO ByteBuffer based buffer.
It is recommended to use
UnpooledByteBufAllocator.directBuffer(int, int),
Unpooled.directBuffer(int) and
Unpooled.wrappedBuffer(ByteBuffer)
instead of calling the constructor explicitly.}
光看介绍,和 UnpooledDirectByteBuf
没有任何区别。它也是 UnpooledDirectByteBuf
的子类。
那么 UnpooledUnsafeDirectByteBuf
和 UnpooledDirectByteBuf
不同处在那里呢?
它们的区别就一点,那就是
UnpooledUnsafeDirectByteBuf
并不是借助NIO
缓存区的方法来获取和设置数据。
而是通过获取NIO
缓存区对象的内存地址memoryAddress
,通过Unsafe
类借助内存地址memoryAddress
直接获取或设置数据。
2.2 setByteBuffer(ByteBuffer buffer, boolean tryFree)
@Override
final void setByteBuffer(ByteBuffer buffer, boolean tryFree) {
super.setByteBuffer(buffer, tryFree);
// 获取 NIO 缓存区buffer 对应的直接内存地址
memoryAddress = PlatformDependent.directBufferAddress(buffer);
}
通过复习 setByteBuffer
方法,获取NIO
缓存区buffer
对应的直接内存地址。
2.3 get
基本数据方法
// 返回对应的内存地址
final long addr(int index) {
return memoryAddress + index;
}
@Override
public byte getByte(int index) {
checkIndex(index);
return _getByte(index);
}
@Override
protected byte _getByte(int index) {
return UnsafeByteBufUtil.getByte(addr(index));
}
@Override
public short getShort(int index) {
checkIndex(index, 2);
return _getShort(index);
}
@Override
protected short _getShort(int index) {
return UnsafeByteBufUtil.getShort(addr(index));
}
....
通过 UnsafeByteBufUtil
对应方法,直接从内存地址获取对应基本类型数据。
2.4 set
基本数据方法
@Override
public ByteBuf setByte(int index, int value) {
checkIndex(index);
_setByte(index, value);
return this;
}
@Override
protected void _setByte(int index, int value) {
UnsafeByteBufUtil.setByte(addr(index), value);
}
@Override
public ByteBuf setShort(int index, int value) {
checkIndex(index, 2);
_setShort(index, value);
return this;
}
@Override
protected void _setShort(int index, int value) {
UnsafeByteBufUtil.setShort(addr(index), value);
}
通过 UnsafeByteBufUtil
对应方法,直接向内存地址设置对应基本类型数据。
2.5 hasMemoryAddress()
@Override
public boolean hasMemoryAddress() {
return true;
}
@Override
public long memoryAddress() {
ensureAccessible();
return memoryAddress;
}
只有这个类型 hasMemoryAddress()
方法才会返回 true
。
2.6 小结
UnpooledUnsafeDirectByteBuf
就是通过直接从内存地址中获取和设置数据的方式,提高性能。
网友评论