ByteBuf from Netty
ByteBuf from Netty比ByteBuffer from java nio更强大,比如可以进行池化,不需要flip切换读写模式。
ByteBuf内部结构如图所示:
ByteBuf本质上是一个字节数组,分为了4个部分。
readerIndex是读指针,每读取一个字节,readerIndex就会+1。一旦readerIndex = writerIndex,不可再读了。
writerIndex是写指针,每写入一个字节,writerIndex就会+1.一旦writerIndex = readerIndex,不可再写了。
capacity表示ByteBuf容量,它的值 = 废弃的字节数 + 可读的字节数 + 可写的字节数。
读取之后,0~readerIndex就被视为废弃的,调用discardReadBytes方法,可以释放这部分空间。
maxCapacity是ByteBuf可以扩容的最大容量。扩容超过maxCapacity就会报错。
示例1
@Test
public void test1(){
ByteBuf b = ByteBufAllocator.DEFAULT.buffer(1,10);
System.out.println("可读字节数:" + b.readableBytes() + ",可写字节数:" + b.writableBytes());
System.out.println("分配了初始容量是9,最大容量10个字节的缓冲区" + b);
b.writeBytes(new byte[]{1,2});
System.out.println("写入了2个字节" + b);
System.out.println("可读字节数:" + b.readableBytes() + ",可写字节数:" + b.writableBytes());
getByteBuf(b);
System.out.println("可读字节数:" + b.readableBytes() + ",可写字节数:" + b.writableBytes());
System.out.println(b);
readByteBuf(b);
System.out.println("可读字节数:" + b.readableBytes() + ",可写字节数:" + b.writableBytes());
System.out.println(b);
getByteBuf(b);
System.out.println("可读字节数:" + b.readableBytes() + ",可写字节数:" + b.writableBytes());
System.out.println(b);
}
private void readByteBuf(ByteBuf buffer){
System.out.println("开始读取,改变了buffer内部指针,buffer内容如下:");
while(buffer.isReadable()){
System.out.print(buffer.readByte() + ",");
}
System.out.println();
}
/**
* 读取字节,不改变指针
* @param buffer
*/
private void getByteBuf(ByteBuf buffer){
System.out.println("开始读取,不改变buffer内部指针,buffer内容如下:");
for (int i = 0; i < buffer.readableBytes(); i++) {
System.out.print(buffer.getByte(i) + ",");
}
System.out.println();
}
运行结果:
可读字节数:0,可写字节数:1
分配了初始容量是9,最大容量10个字节的缓冲区PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 1/10)
写入了2个字节PooledUnsafeDirectByteBuf(ridx: 0, widx: 2, cap: 10/10)
可读字节数:2,可写字节数:8
开始读取,不改变buffer内部指针,buffer内容如下:
1,2,
可读字节数:2,可写字节数:8
PooledUnsafeDirectByteBuf(ridx: 0, widx: 2, cap: 10/10)
开始读取,改变了buffer内部指针,buffer内容如下:
1,2,
可读字节数:0,可写字节数:8
PooledUnsafeDirectByteBuf(ridx: 2, widx: 2, cap: 10/10)
开始读取,不改变buffer内部指针,buffer内容如下:
可读字节数:0,可写字节数:8
PooledUnsafeDirectByteBuf(ridx: 2, widx: 2, cap: 10/10)
toString()
上例中打印ByteBuf的toString()方法中显示的ridx、widx、cap源码如下所示:
abstract class AbstractByteBuf extends ByteBuf
其中capacity()表示ByteBuf容量,它的值 = 废弃的字节数 + 可读的字节数 + 可写的字节数。
@Override
public String toString() {
if (refCnt() == 0) {
return StringUtil.simpleClassName(this) + "(freed)";
}
StringBuilder buf = new StringBuilder()
.append(StringUtil.simpleClassName(this))
.append("(ridx: ").append(readerIndex)
.append(", widx: ").append(writerIndex)
.append(", cap: ").append(capacity());
if (maxCapacity != Integer.MAX_VALUE) {
buf.append('/').append(maxCapacity);
}
ByteBuf unwrapped = unwrap();
if (unwrapped != null) {
buf.append(", unwrapped: ").append(unwrapped);
}
buf.append(')');
return buf.toString();
}
ByteBuf内存回收
Netty的ByteBuf支持缓存池技术,目的是提高频繁创建的ByteBuf的利用率,避免系统内存的浪费。
池子里ByteBuf的回收通过引用计数的方式。每次调用retain()方法,引用+1,每次调用release()方法,引用计数-1。刚创建的ByteBuf计数为1。
如果引用计数=0,
(1)如果是Pooled池化的ByteBuf,继续放入可以重新分配的池子里。
(2)如果是Unpooled未池化的,回收分两种情况:如果是堆结构,JVM垃圾回收。如果是Direct类型,调用本地方法(unsafe.freeMemory)释放内存。
@Test
public void test2(){
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
System.out.println(buffer.refCnt()); //1
buffer.retain();
System.out.println(buffer.refCnt()); //2
buffer.retain();
System.out.println(buffer.refCnt()); //3
buffer.release();
System.out.println(buffer.refCnt()); //2
buffer.retain();
System.out.println(buffer.refCnt()); //3
}
Allocator分配器
ByteBufAllocator有2个重要的实现类:PoolByteBufAllocator(池化的ByteBuf分配器)、UnpooledByteBufAllocator(非池化的ByteBuf分配器)。
PoolByteBufAllocator采用池化技术,能够提高ByteBuf的重复利用率。
UnpooledByteBufAllocator每次用完,通过GC回收。
默认是:
/**
* 使用分配器分配ByteBuf的方法
*/
/**
* 分配初始容量1,最大10的缓冲区
*/
ByteBufAllocator.DEFAULT.buffer(1,10);
/**
* 分配默认256,最大Integer.MAX_VALUE的缓冲区
*/
ByteBufAllocator.DEFAULT.buffer();
/**
* 非池化分配器,分配在堆内存的缓冲区
*/
ByteBufAllocator.DEFAULT.heapBuffer();
/**
* 直接内存缓冲区,不属于堆内存,归属于OS内存
*/
ByteBufAllocator.DEFAULT.directBuffer();
Unpooled类
Unpooled是Netty提供的用来操作缓冲区的工具类。