《Netty实战》学习笔记——ByteBuf篇

本文深入探讨了Netty中的ByteBuf,对比了Java的ByteBuffer,详细介绍了ByteBuf的优点、堆缓冲区、直接缓冲区、复合缓冲区、索引管理、读写操作以及引用计数等核心概念,旨在帮助读者理解Netty如何优化网络数据传输。
摘要由CSDN通过智能技术生成

ByteBuf

Java ByteBuffer

Java NIO支持的字节缓冲区

ByteBuffer的UML图

  • HeapByteBuffer:在jvm堆上面的一个buffer,底层的本质是一个数组。由于内容维护在jvm里,所以把内容写进buffer里速度会快些;并且,可以更容易回收。
  • DirectByteBuffer:底层的数据其实是维护在操作系统的内存中,而不是jvm里,DirectByteBuffer里维护了一个引用address指向了数据,从而操作数据。跟外设(IO设备)打交道时会快很多,因为外设读取jvm堆里的数据时,不是直接读取的,而是把jvm里的数据读到一个内存块里,再在这个块里读取的,如果使用DirectByteBuffer,则可以省去这一步,实现zero copy。题外:外设之所以要把jvm堆里的数据copy出来再操作,不是因为操作系统不能直接操作jvm内存,而是因为jvm在进行gc(垃圾回收)时,会对数据进行移动,一旦出现这种问题,外设就会出现数据错乱的情况
// 分配HeapByteBuffer的方法是:
//参数大小为字节的数量
ByteBuffer.allocate(int capacity); 

// 分配DirectByteBuffer的方法是:
/** 可以看到分配内存是通过unsafe.allocateMemory()来实现的,
  * 这个unsafe默认情况下java代码是没有能力可以调用到的,
  * 不过你可以通过反射的手段得到实例进而做操作,当然你
  * 需要保证的是程序的稳定性,既然叫unsafe的,就是告诉你
  * 这不是安全的,其实并不是不安全,而是交给程序员来操作,
  * 它可能会因为程序员的能力而导致不安全,而并非它本身不安全。
  */
ByteBuffer.allocateDirect(int capacity);
ByteBuffer 属性
byte[] buff  //buff即内部用于缓存的数组。
position //当前读取的位置。
mark //为某一读过的位置做标记,便于某些时候回退到该位置。
capacity //初始化时候的容量。
limit //当写数据到buffer中时,limit一般和capacity相等,当读数据时,limit代表buffer中有效数据的长度。

0 <= mark <= position <= limit <= capacity

ByteBuffer 常规方法
ByteBuffer allocate(int capacity) //创建一个指定capacity的ByteBuffer。
ByteBuffer allocateDirect(int capacity) //创建一个direct的ByteBuffer,这样的ByteBuffer在参与IO操作时性能会更好
ByteBuffer wrap(byte [] array)
ByteBuffer wrap(byte [] array, int offset, int length) //把一个byte数组或byte数组的一部分包装成ByteBuffer。
//get put方法不多说
byte get(int index)
ByteBuffer put(byte b)
int getInt() //从ByteBuffer中读出一个int值。
ByteBuffer putInt(int value)  // 写入一个int值到ByteBuffer中。

// 特殊方法
Buffer clear() // 把position设为0,把limit设为capacity,一般在把数据写入Buffer前调用。
Buffer flip() // 把limit设为当前position,把position设为0,一般在从Buffer读出数据前调用。
Buffer rewind() // 把position设为0,limit不变,一般在把数据重写入Buffer前调用。
compact() // 将 position 与 limit之间的数据复制到buffer的开始位置,复制后 position = limit -position,limit = capacity, 但如果 position 与 limit 之间没有数据的话发,就不会进行复制。
mark() & reset() //通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。
  • put
    写模式下,往buffer里写一个字节,并把postion移动一位。写模式下,一般limit与capacity相等。
  • flip
    写完数据,需要开始读的时候,将postion复位到0,并将limit设为当前postion。
  • get
    从buffer里读一个字节,并把postion移动一位。上限是limit,即写入数据的最后位置。
  • clear
    将position置为0,并不清除buffer内容。
  • mark & reset
    mark相关的方法主要是mark()(标记)和reset()(回到标记)。

在这里插入图片描述

Netty ByteBuf

因为Java NIO的ByteBuffer使用复杂,netty对其重新封装了一层,即ByteBuf。

A random and sequential accessible sequence of zero or more bytes (octets).This interface provides an abstract view for one or more primitive byte arrays ({@code byte[]}) and {@linkplain ByteBuffer NIO buffers}.

ByteBuf 优点
  1. 它可以被用户自定义的缓冲区类型扩展;
  2. 通过内置的复合缓冲区类型实现了透明的零拷贝;
  3. 容量可以按需增长(类似于JDK的StringBuilder);
  4. 在读和写这两种模式之间切换不需要调用ByteBuffer的flip()方法;
  5. 读和写使用了不同的索引;
  6. 支持方法的链式调用;
  7. 支持引用计数;
  8. 支持池化。
ByteBuf 注意点
  • ByteBuf 维护了两个不同的索引:一个用于读取,一个用于写入。当从 ByteBuf 读取时,它的 readerIndex 将会被递增已经被读取的字节数。同样地,当写入 ByteBuf 时,它的 writerIndex 也会被递增。
  • 当读取字节,readerIndex 达到 writerIndex 后继续读取,会触发 IndexOutOfBoundsException。名称以 read 或者 write 开头的 ByteBuf 方法,将会推进其对应的索引,而名称以 set 或者 get 开头的操作则不会。
  • 可以指定 ByteBuf 的最大容量。试图移动写索引(即 writerIndex)超过这个值将会触发一个异常。(默认的限制是 Integer.MAX_VALUE。)

在这里插入图片描述

堆缓冲区

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

/**
  * 支撑数组
  */
ByteBuf heapBuf = ...;
// 检查ByteBuf是否有一个支撑数组
if (heapBuf.hasArray()) {
   
    // 如果有,则获取对该数组的引用
    byte[] array = heapBuf.array();
    // 计算第一个字节的偏移量
    int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();
    // 获得可读字节数
    int length = heapBuf.readableBytes();
    // 使用数组、偏移量和长度作为参数调用方法
    handleArray(array, offset, length);
}
// 当 hasArray()方法返回 false 时,尝试访问支撑数组将触发一个 UnsupportedOperationException。
// 这个模式类似于 JDK 的 ByteBuffer 的用法。
直接缓冲区

ByteBuffer 的 Javadoc 明确指出:“直接缓冲区的内容将驻留在常规的会被垃圾回收的堆之外。”这也就解释了为何直接缓冲区对于网络数据传输是理想的选择。如果数据包含在一个在堆上分配的缓冲区中,那么事实上,在通过套接字发送它之前,JVM将会在内部把堆缓冲区复制到一个直接缓冲区中。

直接缓冲区的主要缺点是,相对于基于堆的缓冲区,它的分配和释放都较为昂贵。

/**
  * 访问直接缓冲区的数据
  */
ByteBuf directBuf = ...;
// 检查ByteBuf是否由数组支撑。如果不是,则是这个是一个直接缓冲区
if (!directBuf.hasArray()) {
   
    // 获取可读字节数
    int length = directBuf.readableBytes();
    // 分配一个新的数组来保存具有该刻度的字节数据
    byte[] array 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值