Netty框架学习之(五):细说数据容器-ByteBuf

1. 简介

字节是网络数据的基本单位。 Java NIO 提供了 ByteBuffer 作为字节容器,但是这个类使用起来过于复杂,而且也有些繁琐。Netty使用了即易于使用又具备良好性能的ByteBuf来替代ByteBuffer。

本文将对ByteBuffer做一个简单的总结。

2. 运作方式与使用模式

2.1 运作方式

因为所有的网络通信都涉及字节序列的移动, 所以高效易用的数据结构明显是必不可少的。
让我们来看看Netty是如何高效的实现这个需求的。

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

如果读取字节直到 readerIndex 达到
和 writerIndex 同样的值时你将会到达“可以读取的”数据的末尾。 如果试图读取超出该点的数据将会触发一个 IndexOutOfBoundsException。

==在ByteBuf中,名称以 read 或者 write 开头的 ByteBuf 方法,将会推进其对应的索引,而名称以 set 或
者 get 开头的操作则不会。==

对于get和set方法,需要传入一个相对索引的位置。当操作基于相对位置的数据超过capacity时,就会引发IndexOutOfBoundsException,例如:capacity是100,当执行byteBuf.setInt(97,100)时,由于int占4个字节(4+97>100),就会触发IndexOutOfBoundsException。

2.2 使用模式

使用ByteBuf的时候,可以选择数据的存放模式,常见的模式如下:

2.2.1 堆缓冲区

最常用的 ByteBuf 模式是将数据存储在 JVM 的堆空间中。这种模式被称为支撑数组
(backing array),它能在没有使用池化的情况下提供快速的分配和释放,非常适合于有遗留的数据需要处理的情况。

public static void main(String args[]) {
        ByteBuf heapBuf = Unpooled.copiedBuffer("heap space",
                CharsetUtil.UTF_8);
        if (heapBuf.hasArray()) { //检查 ByteBuf 是否有一个支撑数组.当 hasArray()方法返回 false 时,
            // 尝试访问支撑数组将触发一个 UnsupportedOperationException。这个模式类似于 JDK 的 ByteBuffer 的用法
            byte[] array = heapBuf.array();
            int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();
            int length = heapBuf.readableBytes();
            System.out.println(Arrays.toString(array));
            System.out.println(offset);
            System.out.println(length);
        } else {
            System.out.println("No Heap Array");
        }
    }

==当 hasArray()方法返回 false 时,尝试访问支撑数组将触发一个 Unsupported
OperationException。这个模式类似于 JDK 的 ByteBuffer 的用法==

2.2.2 直接缓冲区

直接缓冲区是另外一种 ByteBuf 模式.
直接缓冲区的内容并不是驻留在Java的堆上,而是在本地内存中。Java堆上的数据在每次调用本地 I/O 操作之前(或者之后)需要将缓冲区的内容复
制到一个中间缓冲区(或者从中间缓冲区把内容复制到缓冲区),而本地内存避开了这个操作。 这也就解释了为何直接缓冲区对于网络数据传输是理想的选择。如果你的数据包含在一个在堆上分配的缓冲区中,那么事实上,在通过套接字发送它之前, JVM将会在内部把你的缓冲区复制到一个直接缓冲区中。

直接缓冲区的主要缺点是,相对于基于堆的缓冲区,它们的分配和释放都较为昂贵。如果你正在处理遗留代码,你也可能会遇到另外一个缺点:因为数据不是在堆上, 所以你不得不进行一次复制。显然,与使用堆缓冲区相比,这涉及的工作更多。因此,如果事先知道容器中的数据将会被作为数组来访问,你可能更愿意使用堆内存。

直接缓冲区的使用代码样例如下:

public static void main(String args[]) {
        ByteBuf directBuf = Unpooled.directBuffer(100);
        directBuf.writeBytes("direct buffer".getBytes());
        if (!directBuf.hasArray()) {
  //检查 ByteBuf 是否由数组支撑。如果不是,则这是一个直接缓冲区
            int length = directBuf.readableBytes();
            byte[] array = new byte[length];//分配一个新的数组来保存具有该长度的字节数据
            directBuf.getBytes(directBuf.readerIndex(), array);//将字节复制到该数组
            System.out.println(Arrays.toString(array));
            System.out.println(length);
        }
    }

2.2.3 复合缓冲区

复合缓冲区为多个 ByteBuf 提供一个聚合视图,可以根据需要添加或者删除 ByteBuf 实例。实现该功能的类为CompositeByteBuf,它提供了一
个将多个缓冲区表示为单个合并缓冲区的虚拟表示.

CompositeByteBuf 中的 ByteBuf实例可能同时包含直接内存分配和非直接内存分配。
如果其中只有一个实例,那么对 CompositeByteBuf 上的 hasArray()方法的调用将返回该组件上的 hasArray()方法的值;否则它将返回 false。

为了举例说明,让我们考虑一下一个由两部分——头部和主体——组成的将通过 HTTP 协议传输的消息。这两部分由应用程序的不同模块产生,将会在消息被发送的时候组装。该应用程序可以选择为多个消息重用相同的消息主体。当这种情况发生时,对于每个消息都将会创建一个新的头部。
因为我们不想为每个消息都重新分配这两个缓冲区,所以使用CompositeByteBuf 是一个完美的选择。 它在消除了没必要的复制的同时,暴露了通用的 ByteBuf API。

样例代码如下:


                
  • 8
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值