IO编程(五)Netty编程入门(Netty数据传输载体 ByteBuf)

 Netty 里面数据读写是以 ByteBuf 为单位进行交互

ByteBuf结构

  •  ByteBuf 中每读取一个字节,readerIndex 自增1,ByteBuf 里面总共有 writerIndex-readerIndex 个字节可读, 由此可以推论出当 readerIndex 与 writerIndex 相等的时候,ByteBuf 不可读
  • 写数据是从 writerIndex 指向的部分开始写,每写一个字节,writerIndex 自增1,直到增到 capacity,这个时候,表示 ByteBuf 已经不可写了
  • ByteBuf 里面其实还有一个参数 maxCapacity,当向 ByteBuf 写数据的时候,如果容量不足,那么这个时候可以进行扩容,直到 capacity 扩容到 maxCapacity,超过 maxCapacity 就会报错

API

创建ByteBuf

Netty中设计了一个专门负责分配ByteBuf的接口ByteBufAllocator 分配者

具体的层级关系如下图

有了Allocator之后,Netty又为我们提供了两个工具类:Pooled、Unpooled,分类用来分配池化的和未池化的ByteBuf,进一步简化了创建ByteBuf的步骤,只需要调用这两个工具类的静态方法即可。

这里我们使用 ByteBufAllocator.DEFAULT.buffer(int var1, int var2)来创建  其中第一个参数代表初始容量 也就是capacity 第二个代表最大容量 也就是maxCapacity

容量 API

1.capacity()

表示 ByteBuf 底层占用了多少字节的内存(包括丢弃的字节、可读字节、可写字节),不同的底层实现机制有不同的计算方式,后面我们讲 ByteBuf 的分类的时候会讲到

2.maxCapacity()

表示 ByteBuf 底层最大能够占用多少字节的内存,当向 ByteBuf 中写数据的时候,如果发现容量不足,则进行扩容,直到扩容到 maxCapacity,超过这个数,就抛异常

3.readableBytes() 与 isReadable()

readableBytes() 表示 ByteBuf 当前可读的字节数,它的值等于 writerIndex-readerIndex,如果两者相等,则不可读,isReadable() 方法返回 false

4.writableBytes()、 isWritable() 与 maxWritableBytes()

writableBytes() 表示 ByteBuf 当前可写的字节数,它的值等于 capacity-writerIndex,如果两者相等,则表示不可写,isWritable() 返回 false,但是这个时候,并不代表不能往 ByteBuf 中写数据了, 如果发现往 ByteBuf 中写数据写不进去的话,Netty 会自动扩容 ByteBuf,直到扩容到底层的内存大小为 maxCapacity,而 maxWritableBytes() 就表示可写的最大字节数,它的值等于 maxCapacity-writerIndex

/**
 * @Author: xuxu
 * @Date: 2019/12/27 15:10
 */
public class ByteBufTest {
    public static void main(String[] args) {
        //创建ByteBuf默认capacity大小
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        //查询容量包含丢弃的字节,可读字节,可写字节
        System.out.println("容量capacity为:"+buffer.capacity());
        //查询最大容量
        System.out.println("最大容量maxCapacity:"+buffer.maxCapacity());
        //查询可读字节数 writerIndex-readerIndex,如果两者相等,则不可读
        System.out.println("可读字节数readableBytes:"+buffer.readableBytes());
        //是否可读
        System.out.println("是否可读:"+buffer.isReadable());
        //查询可写字节数 capacity-writerIndex,如果两者相等,则表示不可写
        System.out.println("可写字节数writableBytes:"+buffer.writableBytes());
        //是否可写 (这里有可能返回不能写,但是实际上还没有达到maxCapacity,会自动扩容)
        System.out.println("是否可写:"+buffer.isWritable());
    }
}


 

读写指针相关的 API

1.read.erIndex() 与 readerIndex(int)

前者表示返回当前的读指针 readerIndex, 后者表示设置读指针

2.writeIndex() 与 writeIndex(int)

前者表示返回当前的写指针 writerIndex, 后者表示设置写指针

3.markReaderIndex() 与 resetReaderIndex(),markWriterIndex() 与 resetWriterIndex()

前者表示把当前的读指针保存起来,后者表示把当前的读指针恢复到之前保存的值,下面两段代码是等价的

// 代码片段1
int readerIndex = buffer.readerIndex();
// .. 其他操作
buffer.readerIndex(readerIndex);


// 代码片段二
buffer.markReaderIndex();
// .. 其他操作
buffer.resetReaderIndex();

希望大家多多使用代码片段二这种方式,不需要自己定义变量,无论 buffer 当作参数传递到哪里,调用 resetReaderIndex() 都可以恢复到之前的状态,在解析自定义协议的数据包的时候非常常见,

读写 API

 ByteBuf 的读写都可以看作从指针开始的地方开始读写数据

/**
 * @Author: xuxu
 * @Date: 2020/1/3 14:51
 */
public class ByteBufTest02 {
    public static void main(String[] args) {
        //read  wirte 会改变读写指针的位置
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        String msg = "hello";
        System.out.println("可写"+buffer.writableBytes()+"---可读"+buffer.readableBytes());
        System.out.println("================");
        buffer.writeBytes(msg.getBytes());
        System.out.println("writeBytes后");
        System.out.println("可写"+buffer.writableBytes()+"---可读"+buffer.readableBytes());
        ByteBuf byteBuf = buffer.readBytes(buffer.readableBytes());
        System.out.println("readBytes读取到消息:"+byteBuf.toString(Charset.forName("UTF-8")));
        System.out.println("可写"+buffer.writableBytes()+"---可读"+buffer.readableBytes());
        System.out.println("================");
        //set get 不会改写指针的位置
        buffer.setBytes(0, msg.getBytes());
        System.out.println("setBytes后");
        System.out.println("可写"+buffer.writableBytes()+"---可读"+buffer.readableBytes());
        byte[] bytes = new byte[5];
        byteBuf = buffer.getBytes(0, bytes);
        System.out.println("getBytes读取到消息:"+new String(bytes));
        System.out.println("可写"+buffer.writableBytes()+"---可读"+buffer.readableBytes());
    }
}

write read 会改变读写指针的位置.set get方法不会.

release() 与 retain()内存回收

由于 Netty 使用了堆外内存,而堆外内存是不被 jvm 直接管理的,也就是说申请到的内存无法被垃圾回收器直接回收,所以需要我们手动回收。有点类似于c语言里面,申请到的内存必须手工释放,否则会造成内存泄漏。

Netty 的 ByteBuf 是通过引用计数的方式管理的,如果一个 ByteBuf 没有地方被引用到,需要回收底层内存。默认情况下,当创建完一个 ByteBuf,它的引用为1,然后每次调用 retain() 方法, 它的引用就加一, release() 方法原理是将引用计数减一,减完之后如果发现引用计数为0,则直接回收 ByteBuf 底层的内存。

slice()、duplicate()、copy()

/**
 * @Author: xuxu
 * @Date: 2020/1/3 15:47
 */
public class ByteBufTest03 {
    public static void main(String[] args) {
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        buffer.writeBytes("hello".getBytes());
        System.out.println("初始buffer的最大容量是:"+buffer.capacity());
        System.out.println("初始buffer的可写容量是:"+buffer.writableBytes());
        System.out.println("初始buffer的可读容量是:"+buffer.readableBytes());
        ByteBuf sliceBuffer = buffer.slice();
        //slice()复制原始buffer中读写指针间的一部分(有内容的部分)
        System.out.println("======buffer.slice()后产生新的sliceBuffer======");
        System.out.println("sliceBuffer的最大容量是:"+sliceBuffer.capacity());
        System.out.println("sliceBuffer的可写容量是:"+sliceBuffer.writableBytes());
        System.out.println("sliceBuffer的可读容量是:"+sliceBuffer.readableBytes());
        ByteBuf duplicateBuffer = buffer.duplicate();
        //duplicate()复制原始buffer 指针一样
        System.out.println("======buffer.duplicate()后产生新的duplicateBuffer======");
        System.out.println("duplicateBuffer的最大容量是:"+duplicateBuffer.capacity());
        System.out.println("duplicateBuffer的可写容量是:"+duplicateBuffer.writableBytes());
        System.out.println("duplicateBuffer的可读容量是:"+duplicateBuffer.readableBytes());
        ByteBuf copyBuffer = buffer.copy();
        //复制整个buffer  新产生的buffer在内存上和原始互不影响
        System.out.println("======buffer.copy()后产生新的copyBuffer======");
        System.out.println("copyBuffer的最大容量是:"+copyBuffer.capacity());
        System.out.println("copyBuffer的可写容量是:"+copyBuffer.writableBytes());
        System.out.println("copyBuffer的可读容量是:"+copyBuffer.readableBytes());
    }
}

我们分析了 Netty 对二进制数据的抽象 ByteBuf 的结构,本质上它的原理就是,它引用了一段内存,这段内存可以是堆内也可以是堆外的,然后用引用计数来控制这段内存是否需要被释放,使用读写指针来控制对 ByteBuf 的读写,可以理解为是外观模式的一种使用

基于读写指针和容量、最大可扩容容量,衍生出一系列的读写方法,要注意 read/write 与 get/set 的区别

多个 ByteBuf 可以引用同一段内存,通过引用计数来控制内存的释放,遵循谁 retain() 谁 release() 的原则

发布了229 篇原创文章 · 获赞 49 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/baiyan3212/article/details/103732463