netty之ByteBuf

Java NIO 提供了 ByteBuffer 作为它的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐。ByteBuf是对java ByteBuffer的封装。

两个索引

netty之ByteBuf_第1张图片

ByteBuf有两个重要的索引,readerIndex和writeIndex。一个用于读取一个用于写入。这两个值初始值都是0。readerIndex用来标识当前读取位置,当从 ByteBuf 读取时,它的 readerIndex 将会被递增已经被读取的字节数。同样地,当写入 ByteBuf 时,它的writerIndex 也会被递增。这两个索引将这弓buf分成三部分。

1、可丢弃部分(Discardable bytes)

0->readerIndex部分。这部分表示已经被读取过的数据,可以被丢弃。

可以通过discardReadBytes清理已读部分数据。

会丢弃0->readerIndex之间的数据。将readerIndex至writerIndex 之间可读部分数据平移至0位置开始。

2、可读部分(Readable bytes)

readerIndex ->writeIndex 之间的部分,也是实际内容区域。任何名称以read或skip开头的操作方法都将获取或跳过当前readerIndex处的数据,并将readerIndex增加之读字节数大小位置。如果没有足够的数据可读,则会抛出IndexOutOfBoundsException异常。可以用isReadable判断是否有可读数据

ByteBuf buffer = ...;
 while (buffer.isReadable()) {
     System.out.println(buffer.readByte());
 }

3、可写部分(Writable bytes)

writeIndex -> capacity 这部分是可以写入数据的部分。所有以write开头的方法都从writeIndex位置开始写入数据,writeIndex增加写入内容字节大小。如果写入数据超过剩余可写容量,则会抛出IndexOutOfBoundsException异常。可以通过maxWritableBytes方法获取剩余可写最大字节数

 ByteBuf buffer = ...;
 while (buffer.maxWritableBytes() >= 4) {
     buffer.writeInt(random.nextInt());
 }

顺序和随机访问索引

顺序访问不指定索引,buf.readByte(),读取后readIndex会递增。

随机访问buf.getByte(i)按索引位置读取数据,不会引起readIndex的变化。

操作示例

//使用Unpooled创建buf
ByteBuf buf = Unpooled.buffer(10);
System.out.println(buf);
for (int i = 0; i < 6; i++) {
    buf.writeByte(i);//写入数据,writeIndex递增
}
System.out.println(buf);
//随机访问不会改变ridx索引
for (int i = 0; i < 5; i++) {
    buf.getByte(i);
}
System.out.println(buf);
for (int i = 0; i < 5; i++) {
    buf.readByte();//顺序读取,readIndex递增
}
//当前最大可写字节大小
System.out.println(buf.maxWritableBytes());
System.out.println(buf);
buf.discardReadBytes();//丢弃已读取数据部分
System.out.println(buf);
/**
输出内容:
UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 0, cap: 10)
UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 6, cap: 10)
UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 6, cap: 10)
2147483641
UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 5, widx: 6, cap: 10)
UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 1, cap: 10)
**/

内存分配

ByteBuf创建内存区域可以是在JVM内,也可以是直接内存(堆外内存)。如果是堆内存,可以buf.array()获取数据数组,通过buf.hasArray()可以判断是否有数组,间接的可以通过该方法判断是堆内存还是堆外内存。

ByteBuf的分配通过ByteBufAllocator接口进行

netty之ByteBuf_第2张图片

ByteBufAllocator有两个常用的实现类PooledByteBufAllocator和UnpooledByteBufAllocator。一个是buf进行池化另一个非池化。默认情况下DefaultChannelConfig.allocator = ByteBufAllocator.DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR。

来看下ByteBufUtil.DEFAULT_ALLOCATOR的初始化。这一步在ByteBufUtil的静态块里

static {
    String allocType = SystemPropertyUtil.get(
            "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
    allocType = allocType.toLowerCase(Locale.US).trim();

    ByteBufAllocator alloc;
    if ("unpooled".equals(allocType)) {
        alloc = UnpooledByteBufAllocator.DEFAULT;
    } else if ("pooled".equals(allocType)) {
        alloc = PooledByteBufAllocator.DEFAULT;
    } else {
        alloc = PooledByteBufAllocator.DEFAULT;
    }

    DEFAULT_ALLOCATOR = alloc;
    //...
}

这里看到默认会读取io.netty.allocator.type配置,如果未设置,判断系统是否是Android,是的化unpooled非池化,否则pooled池化。无论是池化还是非池化创建出来的ByteBuf使用起来都是一样的。

allocator的指定

除了通过配置“io.netty.allocator.type”来指定是allcator。还可以启动时通过配置ChannelOption.ALLOCATOR来指定allcator。

池化意思就像我们的线程池、数据库连接池一样。buf使用完后进行清理,然后放到对象池中可以重复使用。因为buf的创建特别是堆外内存的创建还是相对来说耗时一些。

是否使用堆外内存(dirrect buff)

UnpooledByteBufAllocator.DEFAULT和PooledByteBufAllocator.DEFAULT的创建都会读取PlatformDependent.directBufferPreferred()值用来判断是否使用堆外内存也就是直接内存。

directBufferPreferred的源码:

DIRECT_BUFFER_PREFERRED = CLEANER != NOOP
                                  && !SystemPropertyUtil.getBoolean("io.netty.noPreferDirect", false);
public static boolean directBufferPreferred() {
    return DIRECT_BUFFER_PREFERRED;
}

CLEANER变量赋值逻辑:

if (!isAndroid()) {
    // only direct to method if we are not running on android.
    // See https://github.com/netty/netty/issues/2604
    if (javaVersion() >= 9) {
        CLEANER = CleanerJava9.isSupported() ? new CleanerJava9() : NOOP;
    } else {
        CLEANER = CleanerJava6.isSupported() ? new CleanerJava6() : NOOP;
    }
} else {
    CLEANER = NOOP;
}

从上面的逻辑可以看出。要开启堆外内存,首先"io.netty.noPreferDirect"要配置成false,默认就是false。然后jdk支持direct buffer释放的Cleaner。

另外还可以通过Unpooled工具类创建buf。从其名字就可得知创建的非池化buf。

参考:
https://netty.io/4.1/api/io/netty/buffer/ByteBuf.html

你可能感兴趣的:(netty,netty)