【Netty4】ByteBuf是什么

相关文章:
ByteBuf是什么》
netty ByteBuf (一)如何创建ByteBuf对象
netty ByteBuf (二) 引用计数对象(reference counted objects)
netty ByteBuf(三)如何释放ByteBuf

1. 初识ByteBuf

网络上数据的基本单位总是字节。java NIO提供了ByteBuffer作为它的字节容器,但是这个类使用起来过于复杂和繁琐。

netty的替代品ByteBuf,一个强大的实现。既解决了JDK API的局限性,又为网络应用程序的开发者提供了更好的api。

ByteBuf维护着两个索引,一个是读索引,一个是写索引。

*      +-------------------+------------------+------------------+
*      | discardable bytes |  readable bytes  |  writable bytes  |
*      |                   |     (CONTENT)    |                  |
*      +-------------------+------------------+------------------+
*      |                   |                  |                  |
*      0      <=      readerIndex   <=   writerIndex    <=    capacity

可以看出:

  • 0到readerIndex 之间的数据是已经丢弃的数据
  • readerIndex 到 writerIndex之间的为可读数据
  • writerIndex 到capacity之间的数据为可写数据

2. ByteBuf类 – netty的数据容器

ByteBuf维护两个不同的索引,一个是读索引(readerIndex),一个是写索引(writerIndex)。当从ByteBuf中读取数据的时候readerIndex索引会递增已经被读取的字节数。同样,当写入ByteBuf的时候,它的writerIndex索引会被递增。下图展示了一个初始容量为16的空ByteBuf的布局和状态

在这里插入图片描述
​ readIndex和writeIndex均为0的16字节的ByteBuf:

  • 1、如果readerIndex和WriterIndex的值一样的时候,如果继续向前读取数据。将会抛出IndexOutOf-BoundsException 。

  • 2、以read或者write开头的ByteBuf方法,将会推进其对应的索引,而名称以set或get开头操作不会修改索引。

  • 3、ByteBuf具有最大的容量值,试图移动写索引超过这个值,将会触发一个异常。其默认的限制为Integer.MAX_VALUE

3. ByteBuf的几种使用方式

3.1 堆缓冲区

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

    /**
     * 测试堆缓冲区的使用  byteBuf分配的内存为Java 的堆内存
     */
    @Test
    public void testHeapByte(){
        ByteBuf byteBuf = Unpooled.buffer();
        //向byteBuf中写入数据
       for(int i= 65;i<65+26;i++){
          byteBuf.writeByte(i);
       }
       //从byteBuf中读取数据
        byte[] bytes = new byte[26];
       byteBuf.readBytes(bytes);
        System.out.println(new String(bytes));
    }

3.2 直接缓冲区

直接缓冲区是另外一种ByteBuf的模式。我们期望用于对象创建的内存分配永远都来自于堆中,NIO 在JDK1.4中引入的ByteBuffer类允许JVM实现通过本地调用来分配内存。这主要是避免在每次调用本地I/O操作之前将缓冲区的数据复制到一个中间缓冲区。使用直接缓冲区的示例代码如下:

    /**
     * 测试直接缓冲区的使用
     */
    @Test
    public void testDirectByte(){
        ByteBuf byteBuf = Unpooled.directBuffer();
        //向byteBuf中写入数据
        for(int i= 65;i<65+26;i++){
            byteBuf.writeByte(i);
        }
        //从byteBuf中读取数据
        byte[] bytes = new byte[26];
        byteBuf.readBytes(bytes);
        System.out.println(new String(bytes));

    }

3.3 复合缓冲区

第三种 也是最后一种模式使用的是符合缓冲区,它为多个ByteBuffer提供一个聚合视图。可以根据实际的需要添加或者删除一个ByteBuf的实例。这个功能是JDK的ByteBuffer实现完全缺失的一个特性。

ByteBuf的子类 – compositeByteBuf实现了这个模式,它提供了一个将多个缓冲区一个统一的视图。

我们考虑一下一个又两部分 — 头部和主题 — 组成的将通过http协议发送的消息。
在这里插入图片描述
示例代码如下:

 /**
     * 测试符合缓冲区的使用
     * 符合缓冲区的如果包含多个缓冲区,那么就会直接返回false
     */
    @Test
    public void testCompositeBuffer() throws IOException {
        CompositeByteBuf byteBufs = Unpooled.compositeBuffer();
        ByteBuf  heapBuf = Unpooled.buffer();


        ByteBuf directBuf = Unpooled.directBuffer();

        heapBuf.readableBytes();



        heapBuf.writeBytes("我是头部信息".getBytes());
        directBuf.writeBytes("我是尾部信息".getBytes());



        //将 buf添加到符合缓冲区中
        byteBufs.addComponents(true,heapBuf);
        byteBufs.addComponents(true,directBuf);

        //从符合缓冲区中读取数据
        byte[] bytes = new byte[byteBufs.readableBytes()];
        byteBufs.readBytes(bytes);
        System.out.println(new String(bytes));



    }

4. ByteBuf的字节级操作

4.1 随机访问索引

跟java的普通数组一样,ByteBuf的索引位置也是从0开始的,第一个字节的索引是0,最后一个字节的索引是capacity()-1。如下面的代码所示:

    @Test
    public void randomAccessByte(){
        ByteBuf byteBuf = Unpooled.buffer(16);
        byteBuf.writeBytes("abcdefg".getBytes());

        System.out.println((char)byteBuf.getByte(2));

    }

使用那些需要一个索引值参数的方法时不会改变readerIndex和writerIndex这两个索引。如果有需要,可以调用readerIndex(index)和writerIndex(index)手动调整。

4.2 顺序访问

由于JDK的ByteBuffer只有一个索引,所以其需要通过调用flip()方法来在读写模式之间进行切换,下图展示了ByteBuf如何被两个索引划分为3个区域。

在这里插入图片描述

4.3 可丢弃字节

可丢弃字节的分段包含了已经被读过的字节。通过调用 方法,可以丢弃他们并回收空间。这个分段的初始大小为0,存储在readIndex中,随着readerIndex的值不断增加而增大。

*  BEFORE discardReadBytes()
*
*      +-------------------+------------------+------------------+
*      | discardable bytes |  readable bytes  |  writable bytes  |
*      +-------------------+------------------+------------------+
*      |                   |                  |                  |
*      0      <=      readerIndex   <=   writerIndex    <=    capacity
*
*
*  AFTER discardReadBytes()
*
*      +------------------+--------------------------------------+
*      |  readable bytes  |    writable bytes (got more space)   |
*      +------------------+--------------------------------------+
*      |                  |                                      |
* readerIndex (0) <= writerIndex (decreased)        <=        capacity

上述是摘录自ByteBuf的注释片段。我们可以清楚的看到,执行完discardBytes()方法后,readIndex的值变为0,且可写区域增大 。

调用discardReadBytes()可以确保可写分段的最大化,但是这里面伴随着内存的复制。建议还是在只有真正需要的时候才那么做。


参考:
《ByteBuf 详解(一)》参考的主体

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值