在上篇文章也提到ByteBuf和它的几种模式,下面就来了解一下ByteBuf的其他方面,及其优点。还有一些关于ByteBuf池化的一些问题。
ByteBuf的几种获取方式
在Netty中可以通过下面三种方法来获取ByteBuf
ByteBufAllocator
可以使用ByteBufAllocator来进行Buffer的按需分配,可以自己定义大小,也可以自己决定是否使用Buffer内存池。
ByteBufAllocator接口的方法如下,可以看到对任意类型的Buffer都可以进行分配操作,也可以规定Buffer的大小,不知道有没有发现它的DEFAULT属性,这个是决定它使用还是不使用内存池的关键,默认是使用内存池。
对于DEFAULT进行源码跟踪,如下标注的序号所示,可以看到序号3是对DEFALUT_ALLOCATOR进行了赋值操作,它的值是从序号1来的,如果是安卓平台会采用非池化,非安卓平台就采用的是池化,序号2只是根据序号1得到的type进行特征值的获取
因为ByteBufAllocator是一个interface,因此我们可以瞧一瞧实现了这个接口的类,可以看到除了抽象ByteBufAllocate,会有一个Pooled和Unpooled,这个就是决定你分配Buffer的时候是否采用内存池。内存池的优点就是可以提高性能,也可以最大限度的减少内存碎片。
因为ByteBufAllocator中含有各种的Buffer分配的方法,因此它的子类都对此做了实现,可以使用这些类来创建ByteBuf
这个是基于ByteBufAllocator的一种方法,ByteBufAllocator的获取不仅仅可以对Pooled和Unpooled实例化获取,也可以从Channel中获取,如下
使用ByteBufAllocator是一种可以比较灵活的分配ByteBuf的一种方式。
Unpooled
在有的时候不能获取到ByteBufAllocator的引用,或者不必要去实例化一个ByteBufAllocator的子类,Netty提供了一种更简单的方法,可以使用Unpooled类,因此它是非内存池化的Buffer。
主要的方法如下
这里面这个复制Buffer的方法还是常常用到的。
ByteBufUtil
可以从类名看出是一个操作ByteBuf的工具类,它是不能直接的创建Buffer,但是可以通过操作数据或者Buffer来获得Buffer。它里面有很多很实用的方法,在平常开发中可以减少写一些重复的代码。
比如它可以以16进制的形式表示Buffer中的内容,当然也可以转回去。也可以和规定字符集的字符串进制转换。还有一些Buffer的copy操作等等
一些派生缓冲区
可以通过获取指定Buffer的派生缓冲区
- duplicate()
- slice() 或 slice(int, int)
- Unpooled.unmodifiableBuffer(…)
- order(ByteOrder)
- readSlice(int)
什么是派生缓冲区,它与原ByteBuf共享内存区域,它的修改也会影响原来的Buffer,如下代码
public class Allocator {
public static void main(String[] args) {
Charset charset = Charset.forName("UTF-8");
ByteBuf byteBuf = Unpooled.copiedBuffer("study netty", charset);
System.out.println("byteBuf:"+byteBuf.toString(charset));
ByteBuf duplicate = byteBuf.duplicate();
System.out.println("duplicate: "+duplicate.toString(charset));
ByteBuf slice = duplicate.slice(0, 8);
System.out.println("slice duplicate: "+slice.toString(charset));
slice.setByte(7, 'a');
System.out.println("操作slice set 7 -> a");
System.out.println("byteBuf:"+byteBuf.toString(charset));
duplicate.setByte(8, 'b');
System.out.println("操作duplicate set 8 -> b");
System.out.println("byteBuf:"+byteBuf.toString(charset));
}
}
运行结果如下图所示,因此可以看出这些派生缓冲区不同于copy操作的缓冲区,copy的缓冲区只是一个副本。
ByteBufHolder
从接口名可以看出它的含义,也就是ByteBuf的持有者,也就是相当于C原因中的结构体,ByteBuf就是这个结构体中的主要内容。它可以将ByteBuf包装在content里面。就和Spring源码中的BeanDefinitionHolder是一样的,它里面不仅仅需要存BeanDefinition,还要存beanName。ByteBufHolder中不仅仅要存ByteBuf,还要存一些其他内容。
它可以从缓冲区池中借用ByteBuf,在不需要的时候可以释放。
ByteBufHolder的一些操作
说一说引用计数
学过Redis的都知道Redis的对象存活判断就是采用的引用计数法,引用计数法实现简单,处理高效,缺点是无法解决循环引用问题。在Netty的Buffer池中也的对象释放时机,也是使用的引用计数法。
如下图无论是ByteBuf还是ByteBufHolder都是ReferenceCounted的子类,因此可以更进一步的判断是采用的引用计数法。
当计数为0时就表明此Buffer实例要被释放(可能不是真正的释放,但是是不可再用了),一般由最后一个访问该对象的哪一方来进行释放操作。
可以看出Netty对ByteBuf的实现还是考虑了很多的。