Netty之ByteBuf介绍(一)

09.Netty之ByteBuf介绍(一)

一、概述

ByteBuf是对NIO中ByteBuffer的增强。相比于ByteBuffer,Netty 的 ByteBuf 具有卓越的功能性和灵活性。

ByteBuf API 的优点

  • 可以被用户自定义的缓冲区类型扩展
  • 通过内置的复合缓冲区类型实现透明的零拷贝
  • 容量可以按需增长
  • 在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip() 方法
  • 在读和写使用了不同的索引
  • 支持方法的链式调用
  • 支持引用计数
  • 支持池化

二、工作原理

ByteBuf 维护了两个不同的索引:一个用于读取,一个用于写入,当你从 ByteBuf 读取时,readIndex 会递增已经被读取的字节数。同样的,当你写入 ByteBuf 时,它的 writeIndex 也会递增。

09.Netty之ByteBuf介绍(一)01.png

readIndex 和 writeIndex 的起始位置都为 0。

如果 readIndex 和 writeIndex 的值相等,也即此时已经到了可读取数据的末尾,就如同达到数组末尾一样,试图读取超出该点的数据将触发 IndexOutOfBoundsException。

名称以read或者write开头的ByteBuf方法,将会推进其对应的索引,而名称以set或者get开关的操作则不会。可以指定ByteBuf的最大容量。(默认的限制是Integer.MAX_VALUE)。

三、ByteBuf创建

可以通过ByteBuf Allocator类中的默认实现来创建一个Bytebuf

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;

public class ByteBufCreateDemo {
    public static void main(String[] args) {
        //在传参时可以指定ByteBuf的容量,如果不指定的默认是256
//        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(10);
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
    }
}
  • 在创建ByteBuf时可以指定容量大小,如果不指定的默认是256

相比于NIO中的ByteBuffer,Netty中的Bytebuf可以动态扩容。

  • 验证动态扩容
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;

public class ByteBufCreateDemo {
    public static void main(String[] args) {
        // 创建一个Bytebuf,默认创建的容量是256
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        System.out.println("添加数据前:" + buffer);
        // 往Bytebuf中写数据
        StringBuilder stringBuilder = new StringBuilder();
        // 故意超过初始容量,验证是否会自动扩容
        for (int i = 0; i < 300; i++) {
            stringBuilder.append("a");
        }
        // 将数据写入ByteBuf
        buffer.writeBytes(stringBuilder.toString().getBytes());
        System.out.println("添加数据后:" + buffer);
    }
}

运行结果

添加数据前:PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 256)
添加数据后:PooledUnsafeDirectByteBuf(ridx: 0, widx: 300, cap: 512)

说明

  • ridx:表示read index,即读取位置;
  • widx:表示write index,即写入位置;
  • cap:表示容量

从程序运行结果可知,将数据写入ByteBuf后容量由之前的256扩容到512,扩大了一倍。

四、ByteBuf的使用模式

ByteBuf共有三种模式:

  • 堆缓冲区模式(Heap Buffer)

    //可以使用下面的代码来创建池化基于堆的 ByteBuf
    ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(16);
    
  • 直接缓冲区模式(Direct Buffer)

    //通过该方法创建的ByteBuf,使用的是基于直接内存的ByteBuf
    ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);
    
  • 复合缓冲区模式(Composite Buffer)

4.1 堆缓冲区模式(Heap Buffer)

堆缓冲区模式又称为支撑数组(backing array)。将数据存放在JVM的堆空间,通过将数据存储在数组中实现。

  • 优点: 由于数据存储在JVM堆中可以快速创建和快速释放,并且提供了数组直接快速访问的方法
  • 缺点: 每次数据与I/O进行传输时,都需要将数据拷贝到直接缓冲区
public class ByteBufHeapBufferDemo {

    public static void main(String[] args) {    
        // 创建一个堆缓冲区
        ByteBuf buffer = Unpooled.buffer(10);
        String s = "waylau";
        buffer.writeBytes(s.getBytes());
        // 检查是否是支撑数组
        if (buffer.hasArray()) {
            // 获取支撑数组的引用
            byte[] array = buffer.array();
            // 计算第一个字节的偏移量
            int offset = buffer.readerIndex() + buffer.arrayOffset();
            // 可读字节数
            int length = buffer.readableBytes();
            printBuffer(array, offset, length);
        }
    }
    //打印出Buffer的信息
    private static void printBuffer(byte[] array, int offset, int len) {
        System.out.println("array:" + array);
        System.out.println("array->String:" + new String(array));
        System.out.println("offset:" + offset);
        System.out.println("len:" + len);
    }
}

运行结果

array:[B@5b37e0d2
array->String:waylau    
offset0
len:6
4.2 直接缓冲区模式(Direct Buffer)

Direct Buffer属于堆外分配的直接内存,不会占用堆的容量。适用于套接字传输过程,避免了数据从内部缓冲区拷贝到直接缓冲区的过程,性能较好。

  • 优点: 使用Socket传递数据时性能很好,避免了数据从Jvm堆内存拷贝到直接缓冲区的过程。提高了性能
  • 缺点: 相对于堆缓冲区而言,Direct Buffer分配内存空间和释放更为昂贵
public class ByteBufDirectBufferDemo {

    public static void main(String[] args) {
        // 创建一个直接缓冲区
        ByteBuf buffer = Unpooled.directBuffer(10);
        String s = "waylau";
        buffer.writeBytes(s.getBytes());
        // 检查是否是支撑数组.
        // 不是支撑数组,则为直接缓冲区
        if (!buffer.hasArray()) {
            // 计算第一个字节的偏移量
            int offset = buffer.readerIndex();
            // 可读字节数
            int length = buffer.readableBytes();
            // 获取字节内容
            byte[] array = new byte[length];
            buffer.getBytes(offset, array);
            printBuffer(array, offset, length);
        }
    }

    //打印出Buffer的信息
    private static void printBuffer(byte[] array, int offset, int len) {
        System.out.println("array:" + array);
        System.out.println("array->String:" + new String(array));
        System.out.println("offset:" + offset);
        System.out.println("len:" + len);
    }
}

运行结果

array:[B@6d5380c2
array->String:waylau
offset:0
len:6

对于涉及大量I/O的数据读写,建议使用Direct Buffer。而对于用于后端的业务消息编解码模块建议使用Heap Buffer。

4.3 复合缓冲区模式(Composite Buffer)

Composite Buffer是Netty特有的缓冲区。本质上类似于提供一个或多个ByteBuf的组合视图,可以根据需要添加和删除不同类型的ByteBuf。

  • 优点:提供了一种访问方式让使用者自由地组合多个ByteBuf,避免了复制和分配新的缓冲区。
  • 缺点:不支持访问其支撑数组。因此如果要访问,需要先将内容复制到堆内存中,再进行访问。

以下示例是复合缓冲区将堆缓冲区和直接缓冲区组合在一起,没有进行任何复制过程,仅仅创建了一个视图而已。

public class ByteBufCompositeBufferDemo {

    public static void main(String[] args) {
        // 创建一个堆缓冲区
        ByteBuf heapBuf = Unpooled.buffer(3);
        String way = "way";
        heapBuf.writeBytes(way.getBytes());
        // 创建一个直接缓冲区
        ByteBuf directBuf = Unpooled.directBuffer(3);
        String lau = "lau";
        directBuf.writeBytes(lau.getBytes());
        // 创建一个复合缓冲区
        CompositeByteBuf compositeBuffer = Unpooled.compositeBuffer(10);
        compositeBuffer.addComponents(heapBuf, directBuf); // 将缓冲区添加到符合缓冲区
        // 检查是否是支撑数组.
        // 不是支撑数组,则为复合缓冲区
        if (!compositeBuffer.hasArray()) {
            for (ByteBuf buffer : compositeBuffer) {
                // 计算第一个字节的偏移量
                int offset = buffer.readerIndex();
                // 可读字节数
                int length = buffer.readableBytes();
                // 获取字节内容
                byte[] array = new byte[length];
                buffer.getBytes(offset, array);
                printBuffer(array, offset, length);
            }
        }
    }

    //打印出Buffer的信息
    private static void printBuffer(byte[] array, int offset, int len) {
        System.out.println("array:" + array);
        System.out.println("array->String:" + new String(array));
        System.out.println("offset:" + offset);
        System.out.println("len:" + len);
    }
}

运行结果

array:[B@4d76f3f8
array->String:way
offset0
len:3
array:[B@2d8e6db6
array->String:lau
offset0
len:3

CompositeByteBuf是一个虚拟的缓冲区,其用途是将多个缓冲区显示为单个合并缓冲区,类似数据库中的视图。

五、池化与非池化

池化的最大意义在于可以重用 ByteBuf,优点有

  • 没有池化,则每次都得创建新的 ByteBuf 实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力
  • 有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率
  • 高并发时,池化功能更节约内存,减少内存溢出的可能

池化功能是否开启,可以通过下面的系统环境变量来设置

-Dio.netty.allocator.type={unpooled|pooled}
  • 4.1 以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现
  • 4.1 之前,池化功能还不成熟,默认是非池化实现

对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力

  • 有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率
  • 高并发时,池化功能更节约内存,减少内存溢出的可能

池化功能是否开启,可以通过下面的系统环境变量来设置

-Dio.netty.allocator.type={unpooled|pooled}
  • 4.1 以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现
  • 4.1 之前,池化功能还不成熟,默认是非池化实现
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值