Netty之ByteBuf解读(上)

简介: Netty之ByteBuf解读

netty中用于进行信息承载和交流的类叫做ByteBuf,从名字可以看出这是Byte的缓存区,是对字节数据的封装

粗略地可以从2个维度进行区分:内存分布内存回收

  • 按照内存分布维度:堆内存字节缓冲区、直接内存字节缓冲区
  • 按照内存回收维度:基于对象池,普通缓冲区

创建简单使用

1. public class test1 {
2. public static void main(String[] args) {
3. ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(10);
4.         log(buffer);
5.     }
6. 
7. private static void log(ByteBuf buffer) {
8. int length = buffer.readableBytes();
9. int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
10. StringBuilder buf = new StringBuilder(rows * 80 * 2)
11.                 .append("read index:").append(buffer.readerIndex())
12.                 .append(" write index:").append(buffer.writerIndex())
13.                 .append(" capacity:").append(buffer.capacity())
14.                 .append(NEWLINE);
15.         appendPrettyHexDump(buf, buffer);
16.         System.out.println(buf.toString());
17.     }
18. }

上面代码创建了一个默认的 ByteBuf(池化基于直接内存的 ByteBuf),初始容量是 10

输出:

read index:0 write index:0 capacity:10

直接内存vs堆内存

可以使用下面的代码来创建池化基于堆的 ByteBuf

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

也可以使用下面的代码来创建池化基于直接内存的 ByteBuf(默认的)

ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);
  • 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
  • 直接内存对 GC 压力小,因为这部分内存不受 JVM 垃圾回收的管理,但也要注意及时主动释放

池化vs非池化

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

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

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

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

  • 4.1 以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现
  • 4.1 之前,池化功能还不成熟,默认是非池化实现

组成

ByteBuf 由四部分组成

最开始读写指针都在 0 位置

  • capacity:它表示ByteBuf的容量大小,即ByteBuf最多能够容纳多少字节数据。
  • readerIndex和writerIndex:它们表示ByteBuf中可读和可写的字节索引位置。
  • maxCapacity:它表示ByteBuf的最大容量大小,即ByteBuf能够扩容的最大限制。

常用写入方法

方法签名 含义 备注
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) 写入字符串

注意

  • 这些方法的未指明返回值的,其返回值都是 ByteBuf,意味着可以链式调用
  • 网络传输,默认习惯是 Big Endian

先写入 4 个字节:

1. ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(10);
2.         buffer.writeBytes(new byte[]{1, 2, 3, 4});
3.         log(buffer);
1. read index:0 write index:4 capacity:10
2.          +-------------------------------------------------+
3.          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
4. +--------+-------------------------------------------------+----------------+
5. |00000000| 01 02 03 04                                     |....            |
6. +--------+-------------------------------------------------+----------------+

再写进一个int整数,也就是四个字节

buffer.writeInt(5);
1. read index:0 write index:8 capacity:10
2.          +-------------------------------------------------+
3.          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
4. +--------+-------------------------------------------------+----------------+
5. |00000000| 01 02 03 04 00 00 00 05                         |........        |
6. +--------+-------------------------------------------------+----------------+

还有一类方法是 set 开头的一系列方法,也可以写入数据,但不会改变写指针位置。

buffer.setByte(4,1);

扩容

再写进一个整数时,容量就不够了(初始容量为10),这个时候就会引发扩容

1. ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(10);
2.         buffer.writeBytes(new byte[]{1, 2, 3, 4});
3.         buffer.writeInt(5);
4.         log(buffer);
5.         buffer.writeInt(6);
6.         log(buffer);

具体的扩容规则:

  • 如果写入后数据大小未超过 512,则选择下一个 16 的整数倍,例如写入后大小为 12 ,则扩容后 capacity 是 16
  • 如果写入后数据大小超过 512,则选择下一个 2^n,例如写入后大小为 513,则扩容后 capacity 是 2^10=1024(2^9=512 已经不够了)
  • 扩容不能超过 max capacity 会报错
相关文章
|
3天前
|
Java API 容器
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf
90 0
|
8月前
|
Java API 开发者
Netty详解ByteBuf
Netty详解ByteBuf
49 0
|
3天前
|
Java API 索引
Netty Review - ByteBuf 读写索引 详解
Netty Review - ByteBuf 读写索引 详解
60 1
|
3天前
|
Java API
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf(二)
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf
31 0
|
3天前
|
API 容器
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf(一)
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf
41 0
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf(一)
|
10月前
|
存储 Java API
Netty实战(五)ByteBuf—Netty的数据容器
网络数据的基本单位总是字节。Java NIO 提供了 ByteBuffer 作为它的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐。**ByteBuffer 替代品是 ByteBuf**,一个强大的实现,既解决了 JDK API 的局限性,又为网络应用程序的开发者提供了更好的 API。
143 0
|
10月前
|
存储 Java Linux
Netty ByteBuf 的零拷贝(Zero Copy)详解
Netty ByteBuf 的零拷贝(Zero Copy)详解
130 0
|
10月前
Netty之ByteBuf解读(下)
Netty之ByteBuf解读(下)
|
10月前
|
Java
Netty之ByteBuf解读(中)
Netty之ByteBuf解读(中)
|
前端开发 算法 Java
Netty组件Future、Promise、Handler、Pipline、ByteBuf
Netty组件Future、Promise、Handler、Pipline、ByteBuf
57 0