Netty(3) ByteBuf

4 篇文章 0 订阅

一. 概述

网络数据的基本单位总是字节,ByteBuf是netty是对字节数据的封装

ByteBuf API 的优点:

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

二. 组成

在这里插入图片描述

ByteBuf 维护了两个不同的索引:一个用于读取,一个用于写入

从 ByteBuf 读取时, 它的 readerIndex 将会被递增已经被读取的字节数。同样地,当你写入 ByteBuf 时,它的 writerIndex 也会被递增。图 5-1 展示了一个空 ByteBuf 的布局结构和状态。

  • 最开始读写指针都在 0 位置
  • 只可以读取 readerIndex 和 writerIndex 之间的数据
  • 如果读取字节readerIndex 和 writerIndex 同样的值时会触发 IndexOutOfBoundsException。

三. 写入

方法签名含义备注
writeBoolean(boolean value)写入 boolean 值用一字节 01|00 代表 true|false
writeByte(int value)写入 byte 值
writeShort(int value)写入 short 值
writeInt(int value)写入 int 值Big Endian,即 0x250,写入后 00 00 02 50
writeIntLE(int value)写入 int 值Little Endian,即 0x250,写入后 50 02 00 00
writeLong(long value)写入 long 值
writeChar(int value)写入 char 值
writeFloat(float value)写入 float 值
writeDouble(double value)写入 double 值
writeBytes(ByteBuf src)写入 netty 的 ByteBuf
writeBytes(byte[] src)写入 byte[]
writeBytes(ByteBuffer src)写入 nio 的 ByteBuffer
int writeCharSequence(CharSequence sequence, Charset charset)写入字符串

四. 读取

    public static void main(String[] args) {
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(99);
        for (int i = 0; i < 100; i++) {
            buffer.writeByte(i);
        }
        System.out.println(buffer.readBoolean()); // 读取当前读索引的Boolean值,读索引加一

        System.out.println(buffer.readByte()); // 读取 当前读索引的字节 读索引加一

        System.out.println(buffer.readInt()); // 读取当前读索引的int 类型的数据  读索引加四

        System.out.println(buffer.readShort()); // 读取当前读索引的 short 类型的数据  读索引加二

        System.out.println(buffer);
    }

五. ByteBuf 的使用模式

1. Heap Buffer 堆缓冲区 (JVM堆内存)

这是最常用的类型,ByteBuf将数据存储在JVM的堆空间,通过将数据存储在数组中实现的

  1. 优点是:由于数据存储在JVM的堆中可以快速创建和快速释放,并且提供了数组的直接快速访问的方法。

  2. 缺点是:每次读写数据都要先将数据拷贝到直接缓冲区再进行传递

使用方式:

ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);
2 Direct Buffer 直接缓冲区 (直接内存)

NIO 在 JDK 1.4 中引入的 ByteBuffer 类允许 JVM 实现通过本地调用来分配内存。这主要是为了避免在每次调用本地 I/O 操作之前(或者之后)将缓冲区的内容复 制到一个中间缓冲区(或者从中间缓冲区把内容复制到缓冲区)。

Direct Buffer在堆之外直接分配内存,直接缓冲区不会占用堆的容量。事实上,在通过套接字发送它之前,JVM将会在内部把你的缓冲 区复制到一个直接缓冲区中。所以如果使用直接缓冲区可以节约一次拷贝

  1. 优点是:在使用Socket传递数据时性能很好,由于数据直接在内存中,不存在从JVM拷贝数据到直接缓冲区的过程,性能好。

  2. 缺点是:相对于基于堆的缓冲区,它们的分配和释放都较为昂贵。如果你 正在处理遗留代码,你也可能会遇到另外一个缺点:因为数据不是在堆上,所以你不得不进行一 次复制

使用方式:

ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);

默认也是使用的直接内存

 ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(10);

六. 扩容

当写入一个 int 整数时,如果容量不够了,这时会引发扩容

扩容规则:

  • 如何写入后数据大小未超过 512,则选择下一个 16 的整数倍,例如写入后大小为 12 ,则扩容后 capacity 是 16
  • 如果写入后数据大小超过 512,则选择下一个 2^n,例如写入后大小为 513,则扩容后 capacity 是 210=1024(29=512 已经不够了)
  • 扩容不能超过 max capacity 会报错

七. 池化

池化的最大意义在于可以重用 ByteBuf

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

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

-Dio.netty.allocator.type={unpooled|pooled}

netty 4.1 以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现

八. 内存回收

由于 Netty 中有堆外内存的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。

  • UnpooledHeapByteBuf 使用的是 JVM 内存,只需等 GC 回收内存即可
  • UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存
  • PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存

回收内存的源码实现,请关注下面方法的不同实现

protected abstract void deallocate()

Netty 这里采用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口

  • 每个 ByteBuf 对象的初始计数为 1
  • 调用 release 方法计数减 1,如果计数为 0,ByteBuf 内存被回收
  • 调用 retain 方法计数加 1,表示调用者没用完之前,其它 handler 即使调用了 release 也不会造成回收
  • 当计数为 0 时,底层内存会被回收,这时即使 ByteBuf 对象还在,其各个方法均无法正常使用
Netty中的ByteBuf是一个可扩展的字节容器,它提供了一系列的API来方便地读取和写入字节数据。下面是一些常见的ByteBuf使用API: 1. 创建ByteBuf对象 可以使用Unpooled工具类来创建ByteBuf对象,例如: ```java ByteBuf buf = Unpooled.buffer(10); ``` 上面的代码创建了一个容量为10的ByteBuf对象。 2. 写入数据 可以使用write方法向ByteBuf中写入数据,例如: ```java buf.writeByte(1); buf.writeShort(2); buf.writeInt(3); buf.writeLong(4); buf.writeFloat(5.0f); buf.writeDouble(6.0); buf.writeBytes("hello".getBytes()); ``` 上面的代码依次向ByteBuf中写入了一个字节、一个短整型、一个整型、一个长整型、一个单精度浮点数、一个双精度浮点数和一个字符串。 3. 读取数据 可以使用read方法从ByteBuf中读取数据,例如: ```java byte b = buf.readByte(); short s = buf.readShort(); int i = buf.readInt(); long l = buf.readLong(); float f = buf.readFloat(); double d = buf.readDouble(); byte[] bytes = new byte[5]; buf.readBytes(bytes); String str = new String(bytes); ``` 上面的代码依次从ByteBuf中读取了一个字节、一个短整型、一个整型、一个长整型、一个单精度浮点数、一个双精度浮点数和一个字符串。 4. 获取可读字节数 可以使用可读字节数方法来获取当前ByteBuf中可读的字节数,例如: ```java int readableBytes = buf.readableBytes(); ``` 5. 释放ByteBuf 使用完ByteBuf对象后,需要手动调用release方法释放对象,例如: ```java buf.release(); ``` 上面的代码释放了ByteBuf对象,释放后的ByteBuf不能再被使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值