Netty 揭秘CompositeByteBuf:零拷贝优化核心技术

CompositeByteBuf 类

核心设计目标​

  • ​虚拟缓冲区​​:将多个 ByteBuf 合并为单一逻辑视图,减少数据复制。
  • ​零拷贝优化​​:通过组合而非复制提升性能。
  • ​引用计数管理​​:统一管理底层 ByteBuf 的生命周期。

核心成员变量​

  • components​:Component[] 数组,存储所有组成缓冲区。
  • componentCount​:当前有效组件数量(≤ maxNumComponents)。
  • lastAccessed​:缓存最近访问的 Component,加速连续访问。
  • freed​:标记缓冲区是否已释放。

Component

ComponentCompositeByteBuf 的内部类,​​代表组合缓冲区中的一个逻辑段​​,其核心职责是:

属性/方法作用
srcBuf原始添加的 ByteBuf(保留引用计数)
buf解包后的底层数据源(如 UnpooledHeapByteBuf
offset/endOffset当前段在组合缓冲区的起始/结束索引(逻辑坐标)
adjustment段内索引转换偏移量:物理索引 = 逻辑索引 + adjustment
slice缓存该段数据的只读视图(懒加载)

 ​

private static final class Component {final ByteBuf srcBuf;  // 原始缓冲区final ByteBuf buf;     // 解包后的底层缓冲区int adjustment;        // 索引计算偏移量int offset;            // 在组合缓冲区的起始位置int endOffset;         // 在组合缓冲区的结束位置int idx(int index) {return index + adjustment; // 物理索引计算}void free() {srcBuf.release(); // 释放原始缓冲区的引用计数}
}
  • ​索引转换​​:idx() 方法实现逻辑索引到物理索引的转换
  • ​资源释放​​:释放时关注原始缓冲区而非解包后的缓冲区

CompositeByteBuf 的复合结构​

┌───────────┬───────────┬───────────┐
│ Component │ Component │ Component │
│   (buf1)  │   (buf2)  │   (buf3)  │
├───────────┴───────────┴───────────┤
│      CompositeByteBuf 虚拟视图      │
└───────────────────────────────────┘
  • ​索引连续性实现​​:
    • offset 链​​:每个组件的 endOffset = 下一个组件的 offset
      // 组件添加时的偏移计算
      int nextOffset = cIndex > 0 ? components[cIndex-1].endOffset : 0;
      c.reposition(nextOffset); // 设置当前组件的offset

 深入解析 Component 字段设计

理解这些字段的关键在于区分​​逻辑视图​​和​​物理存储​​的差异,以及处理​​多层缓冲区包装​​的场景。以下是详细解释:

srcBuf vs buf - 处理多层包装

final ByteBuf srcBuf; // 用户原始添加的缓冲区
final ByteBuf buf;    // 解包后的底层物理缓冲区

​设计原因​​:

  1. ​引用计数管理​​:

    • srcBuf 是用户直接添加的对象,负责引用计数
    • 释放资源时必须释放 srcBuf,确保正确管理用户提供的缓冲区生命周期

示例代码:

ByteBuf base = Unpooled.buffer(100); // 底层物理缓冲区
ByteBuf sliced = base.slice(10, 20); // 包装缓冲区// 添加到CompositeByteBuf时:
Component comp = new Component(srcBuf: sliced,  // 用户添加的包装器buf: base        // 解包后的物理存储
);

双重调整值:srcAdjustmentadjustment

int srcAdjustment; // 逻辑索引到srcBuf的偏移
int adjustment;    // 逻辑索引到底层buf的偏移

​工作关系​​:

┌───────────────────────┐
│  CompositeByteBuf      │
│  逻辑索引: index       │
├───────────────────────┤
│  srcBuf (用户添加)      │
│  物理索引: index + srcAdjustment
├───────────────────────┤
│  buf (物理存储)         │
│  物理索引: index + adjustment
└───────────────────────┘

​典型场景​​:

// 用户添加一个切片缓冲区:
ByteBuf base = ByteBufAllocator.DEFAULT.buffer(100);
ByteBuf slice = base.slice(10, 50); // 使用base[10-59]// 添加到CompositeByteBuf起始位置0
Component comp = new Component(srcBuf: slice,buf: base,srcAdjustment: 10, // 0→10, 1→11...adjustment: 10,     // 同上offset: 0,         // 在Composite中的起始位置endOffset: 50       // 结束位置
);

slice - 缓存优化

private ByteBuf slice; // 组件数据的只读视图

​设计考虑​​:

  • 懒加载:首次访问时通过 srcBuf.slice() 创建
  • 避免重复计算:后续访问直接返回缓存
  • 释放安全:组件释放时置为 null

为什么需要如此设计?

  1. ​处理任意包装层数​​:

    • 用户可能添加 SlicedByteBuf(WrappedByteBuf(PooledByteBuf))
    • 需要穿透所有包装层直达物理存储
  2. ​生命周期分离​​:

    • 必须通过原始 srcBuf 释放资源
    • 但读写操作使用底层 buf 更高效
  3. ​逻辑/物理映射​​:

    • offset/endOffset 维护虚拟空间连续性
    • adjustment/srcAdjustment 解决物理偏移

这种设计确保了:

  • 引用计数正确性(通过 srcBuf)
  • 操作高效性(直接操作底层 buf)
  • 位置映射准确性(双重 adjustment)
  • 资源优化(懒加载 slice)

​总结​​:Component 本质是三层映射关系的管理器:
​用户视图(srcBuf)​​ ↔ ​​逻辑视图(offset)​​ ↔ ​​物理存储(buf)​
通过双重 adjustment 系统完美解决多层包装缓冲区的定位问题。

newComponent 方法:解包机制详解

这个方法的核心目标是​​去除所有中间包装层​​,获取最底层的原始 ByteBuf,并计算正确的物理索引位置。以下是逐层解包的过程:


1. ​​基础准备​

final int srcIndex = buf.readerIndex();  // 原始缓冲区的读索引位置
final int len = buf.readableBytes();     // 可读字节数

2. ​​解包循环:去除包装层​

ByteBuf unwrapped = buf;
int unwrappedIndex = srcIndex;
while (unwrapped instanceof WrappedByteBuf || unwrapped instanceof SwappedByteBuf) {unwrapped = unwrapped.unwrap();
}
  • ​处理类型​​:
    • WrappedByteBuf:装饰器模式包装的缓冲区
    • SwappedByteBuf:字节序转换包装的缓冲区
  • ​目的​​:去除所有中间包装层,获取底层核心缓冲区
  • ​索引调整​​:unwrappedIndex 保持原始读索引位置(包装层不改变数据位置)

3. ​​特殊类型解包:切片/复制缓冲区​

// 处理切片缓冲区(SlicedByteBuf)
if (unwrapped instanceof AbstractUnpooledSlicedByteBuf) {unwrappedIndex += ((AbstractUnpooledSlicedByteBuf) unwrapped).idx(0);unwrapped = unwrapped.unwrap();
} 
// 处理池化切片缓冲区
else if (unwrapped instanceof PooledSlicedByteBuf) {unwrappedIndex += ((PooledSlicedByteBuf) unwrapped).adjustment;unwrapped = unwrapped.unwrap();
} 
// 处理复制缓冲区(DuplicatedByteBuf)
else if (unwrapped instanceof DuplicatedByteBuf || unwrapped instanceof PooledDuplicatedByteBuf) {unwrapped = unwrapped.unwrap();
}
  • ​关键操作​​:
    • ​切片缓冲区​​:调整物理索引 (unwrappedIndex += adjustment)
    • ​复制缓冲区​​:直接解包(无索引调整)
  • ​为什么需要调整​​:
    • 切片缓冲区是原始缓冲区的子集
    • adjustment 是切片在原始缓冲区中的起始偏移
    • 示例:原始缓冲区 [0-100] → 切片 [10-50],则 adjustment = 10

4. ​​切片优化判断​

final ByteBuf slice = buf.capacity() == len ? buf : null;
  • ​优化逻辑​​:
    • 如果可读范围 (len) 等于整个缓冲区容量 → 直接使用原始缓冲区
    • 否则标记为 null(后续按需创建切片)

5. ​​创建 Component​

return new Component(buf.order(ByteOrder.BIG_ENDIAN),  // 原始缓冲区(统一字节序)srcIndex,                         // 原始读索引unwrapped.order(ByteOrder.BIG_ENDIAN), // 解包后的底层缓冲区unwrappedIndex,                   // 计算后的物理索引offset,                           // 在组合缓冲区中的逻辑偏移len,                              // 数据长度slice                             // 优化后的切片(可能为null)
);


解包设计要点

  1. ​深度解包​​:确保获取最底层数据源
  2. ​索引修正​​:
    • 累计所有切片偏移 (adjustment)
    • 保持原始读索引 (srcIndex)
  3. ​字节序统一​​:强制转为 BIG_ENDIAN
  4. ​切片优化​​:避免不必要的二次切片

通过这种设计,CompositeByteBuf 能够:

  • 正确处理任意复杂的缓冲区包装结构
  • 精确定位底层物理数据位置
  • 保持零拷贝特性(不复制实际数据)

​​索引转换过程详解​

全局索引转换​​:通过二分查找定位物理组件

// 定位逻辑索引对应的组件
Component findIt(int offset) {// 使用 lastAccessed 缓存优化for (int low = 0, high = componentCount; low <= high;) {int mid = low + high >>> 1;Component c = components[mid];if (c == null) {throw new IllegalStateException("No component found for offset. " +"Composite buffer layout might be outdated, e.g. from a discardReadBytes call.");}if (offset >= c.endOffset) {low = mid + 1;} else if (offset < c.offset) {high = mid - 1;} else {lastAccessed = c;return c;}}
}

当访问逻辑位置 index 时:

​示例计算​​:

// 假设有两个组件:
// Component1: offset=0, endOffset=20, adjustment=0
// Component2: offset=20, endOffset=50, adjustment=-20// 访问 index=25:
Component comp = findComponent(25); // 返回 Component2
int physicalIdx = comp.idx(25);    // 25 + (-20) = 5
byte value = comp.buf.getByte(5);  // 实际读取 buf2[5]

关键复合操作解析​

操作实现机制
​添加组件​动态扩展 Component[],更新后续组件的 offset
删除组件removeComponent(int index),触发后续组件偏移更新
​跨组件读取​自动拆分请求到多个组件(如 getBytes() 遍历相关组件)
​缓冲区合并​consolidate() 复制数据到新缓冲区,减少组件数量
​释放已读数据​discardReadComponents() 释放头部组件并更新全局偏移
​零拷贝暴露​nioBuffers() 返回底层 ByteBuffer 数组,避免数据复制
偏移量→组件索引转换toComponentIndex(int offset)
获取偏移量所在组件componentAtOffset(int offset)

    设计优势​

    1. ​零拷贝​​:逻辑聚合避免数据复制
    2. ​动态扩展​​:组件数组按需扩容(类似 ArrayList
    3. ​高效定位​​:二分查找 + 访问缓存
    4. ​生命周期统一​​:通过 srcBuf 统一管理原始缓冲区的引用计数

    核心设计亮点分析

    1. ​分层索引系统​

      • 逻辑索引 (用户视角) → 组件索引 → 物理索引 (底层缓冲区)
      • 通过 offset/endOffset + adjustment 实现映射
    2. ​写时优化策略​

      • 延迟合并:直到组件数超过阈值才触发合并
      • 懒加载:Component.slice 延迟创建切片视图
    3. ​异常安全设计​

      • 资源获取即保护:addComponent0() 中的 try-finally 确保失败时释放资源
      • 溢出预防:所有容量计算前进行 checkForOverflow
    4. ​对象池应用​

      • RecyclableArrayList 减少临时集合分配
      • 组件数组动态扩容而非固定大小

    性能关键路径

    1. ​读取路径优化​

      用户getByte() → 缓存检查 → 二分查找 → 直接底层访问
    2. ​写入路径优化​

      跨组件写入 → 自动拆分 → 边界处理 → 只更新受影响组件
    3. ​I/O 路径优化​

      nioBuffers() → 获取底层ByteBuffer数组 → 零拷贝传输到Channel

    该实现通过精细的索引管理、惰性合并策略和零拷贝优化,在保证功能完整性的同时实现了高性能的虚拟缓冲区聚合。

    典型应用场景​

    // 组合多个缓冲区
    ByteBuf header = Unpooled.copiedBuffer("HEADER", CharsetUtil.UTF_8);
    ByteBuf body = Unpooled.copiedBuffer("BODY", CharsetUtil.UTF_8);
    CompositeByteBuf composite = Unpooled.compositeBuffer().addComponent(header).addComponent(body);// 透明读取(自动跨组件)
    byte[] data = new byte[composite.readableBytes()];
    composite.getBytes(0, data); // 无需关心header/body分界

    ​关键理解​​:Component 是物理缓冲区的逻辑映射代理,CompositeByteBuf 通过维护组件的偏移链,实现了虚拟连续地址空间。这种设计完美平衡了内存效率与操作便利性。

    总结

    ​CompositeByteBuf 通过高效管理多个 ByteBuf 的元数据和索引,实现了零拷贝的虚拟缓冲区视图。其核心创新点包括:​

    1. ​动态组件合并​​:在组件数量超过阈值时自动合并,平衡性能与内存。
    2. ​二分查找 + 缓存​​:高效定位物理索引。
    3. ​生命周期统一管理​​:确保底层缓冲区正确释放。
    4. ​零拷贝优化​​:通过 nioBuffers() 等方法支持高效 I/O 操作。

    ​适用场景​​:需要聚合多个分散缓冲区的网络协议处理(如 HTTP 分块传输)。

    addComponent与索引调整机制

    addComponent()​不仅限于尾部添加​​,而是支持任意位置的插入:

    // 尾部添加(最常见)
    composite.addComponent(buffer); // 指定位置添加(引发索引重排)
    composite.addComponent(1, middleBuffer);  // 在索引1处插入新组件

    索引调整的核心场景​

    当发生​​非尾部插入​​或​​组件删除​​时,需要全局索引重排:

    场景示意图影响
    ​中部插入​[A][C] → 插入B → [A][B][C]C的偏移量需增加B的长度
    ​头部插入​[B][C] → 插入A → [A][B][C]B和C的偏移量需增加A的长度
    ​组件删除​[A][B][C] → 删除B → [A][C]C的偏移量需减少B的长度

    addComponent0() 中的关键逻辑:

    // 在addComponent0方法中
    if (cIndex < componentCount - 1) {  // 非尾部插入updateComponentOffsets(cIndex); // 重排后续组件偏移量
    } else if (cIndex > 0) {            // 尾部插入但非首组件c.reposition(components[cIndex-1].endOffset); // 接续前组件
    }

    索引重排核心方法 updateComponentOffsets()【进行了可读性修改】:

    private void updateComponentOffsets(int startIndex) {int nextOffset = startIndex > 0 ? components[startIndex-1].endOffset : 0;for (int i = startIndex; i < componentCount; i++) {Component c = components[i];c.reposition(nextOffset);      // 重设当前组件偏移nextOffset = c.endOffset;       // 传递到下一组件}
    }

    reposition把最终的offset增加了,因此调整的 adjustment要对应减少,使得实际索引不变

            void reposition(int newOffset) {int move = newOffset - offset;endOffset += move;srcAdjustment -= move;adjustment -= move;offset = newOffset;}

    设计哲学​

    1. ​逻辑连续性​​:通过动态维护offset/endOffset链,实现虚拟连续地址空间
    2. ​操作完备性​​:支持任意位置的组件增删,保持缓冲区一致性
    3. ​惰性优化​​:仅在必要时触发索引重排(如非尾部插入)
    4. ​物理隔离​​:各组件保持独立内存,避免大规模数据移动

    ​关键洞察​​:CompositeByteBuf 本质上是一个"组件链表管理器",通过精确维护每个组件的偏移元数据,实现多缓冲区的无缝逻辑拼接。这种设计在保持零拷贝优势的同时,提供了灵活的缓冲区操作能力。

    nioBuffers() 的零拷贝实现解析

    CompositeByteBuf.nioBuffers() (返回ByteBuffer[]数组的重载版本)是 ​​真正的零拷贝操作​​,它直接返回底层 ByteBuffer 的视图而不复制数据。核心原理如下:

    RecyclableArrayList 指的是这个List对象被一直复用

    public ByteBuffer[] nioBuffers(int index, int length) {RecyclableArrayList buffers = RecyclableArrayList.newInstance();try {int i = toComponentIndex0(index);while (length > 0) {Component c = components[i];int localLength = Math.min(length, c.endOffset - index);switch (c.buf.nioBufferCount()) {case 1:  // 单个ByteBufferbuffers.add(c.buf.nioBuffer(c.idx(index), localLength));break;default: // 复合缓冲区返回多个ByteBufferCollections.addAll(buffers, c.buf.nioBuffers(c.idx(index), localLength));}index += localLength;length -= localLength;i++;}return buffers.toArray(EmptyArrays.EMPTY_BYTE_BUFFERS);} finally {buffers.recycle();}
    }

    当组件本身也是复合缓冲区时:

    // 组件是CompositeByteBuf时的处理
    default: Collections.addAll(buffers, c.buf.nioBuffers(c.idx(index), localLength));

    此时会​​递归调用​​组件的 nioBuffers(),保持零拷贝特性


     

    ByteBuf的nioBuffe

    ByteBuf的nioBuffer方法通常不进行数组拷贝,而是采用包装(wrap)的方式来提高性能。

    nioBuffer(int index, int length) 方法的作用是:

    1. ​将当前缓冲区的子区域转换为 NIO ByteBuffer

      • 从指定的 index 开始,截取 length 长度的数据,生成一个 NIO 标准的 ByteBuffer
      • 返回的 ByteBuffer​可能共享底层数据(零拷贝)​​,也可能是数据的​​独立副本​​(取决于实现)。
    2. ​不影响原缓冲区的状态​

      • ​不修改​​原缓冲区的 readerIndexwriterIndex
      • 修改返回的 ByteBufferpositionlimit​不会影响​​原缓冲区的索引或标记。
    3. ​动态缓冲区的限制​

      • 如果原缓冲区是动态的(容量可调整),后续扩容/缩容后,返回的 ByteBuffer​不会自动同步​​新内容。
    4. ​可能抛出异常​

      • 如果底层实现不支持共享内容(如某些堆外内存或复合缓冲区),会抛出 UnsupportedOperationException

    例如UnpooledHeapByteBuf

        @Overridepublic ByteBuffer nioBuffer(int index, int length) {ensureAccessible();return ByteBuffer.wrap(array, index, length).slice();}

    wrap调用

        public static ByteBuffer wrap(byte[] array,int offset, int length){try {return new HeapByteBuffer(array, offset, length, null);} catch (IllegalArgumentException x) {throw new IndexOutOfBoundsException();}}

    HeapByteBuffer都没有拷贝

        public ByteBuffer slice() {int pos = this.position();int lim = this.limit();int rem = (pos <= lim ? lim - pos : 0);return new HeapByteBuffer(hb,-1,0,rem,rem,pos + offset, segment);}

    关键点分析:

    • 使用 ByteBuffer.wrap(array, index, length) - 这不是拷贝,而是包装
    • 调用 .slice() 创建视图 - 这也不是拷贝,而是共享底层数据的视图

    在Netty的其他ByteBuf实现中,可能存在拷贝的情况:

    1. CompositeByteBuf - 当需要将多个不连续的ByteBuf合并为单个ByteBuffer时
    2. 跨不同内存区域的ByteBuf - 当堆内存和直接内存需要统一表示时
    3. 特定的安全要求 - 当需要防止外部修改内部数据时

    性能考量:

    • 通过返回多个ByteBuffer而不是合并成一个,避免了大量的内存拷贝
    • 在网络I/O操作中,可以使用gathering write一次性写入多个缓冲区

    这种设计体现了Netty在性能优化方面的考虑,尽可能避免不必要的数据拷贝操作。

    nioBufferCount不等于1的情况

    当前UnpooledHeapByteBuf的实现

    @Override
    public int nioBufferCount() {return 1;
    }
    

    UnpooledHeapByteBuf总是返回1,因为它基于单一的连续字节数组。

    以下情况会返回大于1的nioBufferCount:

    CompositeByteBuf

    // 从测试代码可以看出的使用模式
    CompositeByteBuf comp = compositeBuffer(256);
    ByteBuf buf = directBuffer().writeBytes("buf1".getBytes(CharsetUtil.US_ASCII));
    for (int i = 0; i < 65; i++) {comp.addComponent(true, buf.copy());  // 添加65个组件
    }
    // 这种情况下 nioBufferCount() 会返回 65
    

    ChannelOutboundBuff

    从测试代码 ChannelOutboundBufferTest.java 可以看到:

    @Test
    public void testNioBuffersExpand() {// 添加64个ByteBuffor (int i = 0; i < 64; i++) {buffer.addMessage(buf.copy(), buf.readableBytes(), channel.voidPromise());}buffer.addFlush();assertEquals(64, buffer.nioBufferCount());  // 返回64个NioBuffer
    }
    

    主要原因总结

    nioBufferCount > 1的根本原因:

    1. 组合型ByteBuf - 由多个独立的ByteBuf组成
    2. 分段存储 - 数据分布在多个不连续的内存区域
    3. 性能优化 - 避免大量数据的合并拷贝,保持原有的分段结构

    consolidate(int cIndex, int numComponents) 

    方法签名

    public CompositeByteBuf consolidate(int cIndex, int numComponents) {checkComponentIndex(cIndex, numComponents);consolidate0(cIndex, numComponents);return this;
    }

    ​作用​​:将指定范围内的连续组件合并为单个缓冲区,减少组件数量以提高性能。

    ​参数​​:

    • cIndex:起始组件的索引
    • numComponents:要合并的组件数量


    consolidate0(int cIndex, int numComponents)

    1. 若只需合并 ≤1 个组件,无需操作直接返回

    2. 计算合并范围

    final int endCIndex = cIndex + numComponents;
    final int startOffset = cIndex != 0 ? components[cIndex].offset : 0;
    final int capacity = components[endCIndex - 1].endOffset - startOffset;

    ​变量说明​​:

    • endCIndex:结束组件的索引(开区间)
    • startOffset:合并起始位置(首个组件的偏移量)
    • capacity:新缓冲区所需容量 = 末组件结束位置 - 起始位置

    ​示例​​:

    组件布局: [A(0-10)][B(10-20)][C(20-30)][D(30-40)]
    调用:consolidate0(1, 2) → 合并B和C
    计算:endCIndex = 1+2 = 3startOffset = B.offset = 10capacity = C.endOffset(30) - 10 = 20

    3. 创建新缓冲区

    final ByteBuf consolidated = allocBuffer(capacity);

    ​实现​​:

    • allocBuffer() 根据配置分配堆/直接内存缓冲区
    • 上例中创建容量为20字节的新缓冲区

    4. 数据迁移

    for (int i = cIndex; i < endCIndex; i++) {components[i].transferTo(consolidated);
    }

    ​核心操作​​:

    • 遍历组件范围(cIndexendCIndex-1
    • transferTo() 执行zhi'j:
      // Component内部方法
      void transferTo(ByteBuf dst) {dst.writeBytes(buf, idx(offset), length());free(); // 释放原始组件资源
      }

    ​效果​​:
    所有选定组件的数据被复制到新缓冲区,原组件资源被释放

    5. 清理状态

    lastAccessed = null;
    removeCompRange(cIndex + 1, endCIndex);

    ​作用​​:

    • 重置缓存组件(合并后布局变化)
    • 移除冗余组件:保留起始位置组件(cIndex),删除后续组件

    ​上例结果​​:

    原始: [A][B][C][D]
    删除后: [A][B] (C,D被移除)

    6. 替换组件

    components[cIndex] = newComponent(consolidated, 0);

    ​关键操作​​:

    • 用新缓冲区创建单一组件:
      Component newComponent(ByteBuf buf, int offset) {return new Component(buf, 0, buf, 0, offset, capacity, null);
      }
    • 替换原起始位置组件

    ​上例结果​​:

    [A][新组件(合并B+C)]

    7. 偏移量更新

    if (cIndex != 0 || numComponents != componentCount) {updateComponentOffsets(cIndex);
    }

    ​条件​​:

    • 非头部合并 (cIndex != 0) 或 非全量合并时
    • 需要更新后续组件的偏移量

    ​更新逻辑​​:

    void updateComponentOffsets(int fromIndex) {int nextOffset = fromIndex > 0 ? components[fromIndex-1].endOffset : 0;for (int i = fromIndex; i < componentCount; i++) {components[i].reposition(nextOffset);nextOffset = components[i].endOffset;}
    }

    ​上例最终结构​​:

    组件A: offset=0, endOffset=10
    新组件: offset=10, endOffset=30 (原B+C)
    组件D: offset自动更新为30, endOffset=40

    合并操作效果示意图

    合并前:
    ┌───────┬───────┬───────┬───────┐
    │   A   │   B   │   C   │   D   │
    │ 0-10  │10-20  │20-30  │30-40  │
    └───────┴───────┴───────┴───────┘合并B+C后:
    ┌───────┬───────────────┬───────┐
    │   A   │   NEW(BC)     │   D   │
    │ 0-10  │   10-30       │30-40  │  ← D自动偏移到30
    └───────┴───────────────┴───────┘

    设计要点

    1. ​性能平衡​​:减少组件数量提升操作效率
    2. ​资源管理​​:
      • 合并时释放原组件资源
      • 通过free()确保引用计数正确
    3. ​无缝衔接​​:
      • 自动维护偏移量连续性
      • 不影响合并范围外的组件
    4. ​内存优化​​:
      • 按需分配新缓冲区
      • 及时清除冗余组件引用

    ​适用场景​​:高频读写操作前,减少组件数量以提升性能

      

      discardReadComponents() - 丢弃已读组件​

      public CompositeByteBuf discardReadComponents() {ensureAccessible();final int readerIndex = readerIndex();if (readerIndex == 0) return this;  // 无数据可丢弃int writerIndex = writerIndex();// 情况1: 所有数据都已读 (readerIndex == writerIndex == capacity)if (readerIndex == writerIndex && writerIndex == capacity()) {for (int i = 0; i < componentCount; i++) {components[i].free();  // 释放所有组件}lastAccessed = null;clearComps();  // 清空组件数组setIndex(0, 0); // 重置读写索引adjustMarkers(readerIndex); // 调整标记位return this;}// 情况2: 部分数据已读int firstComponentId = 0;Component c = null;// 定位首个未完全读取的组件for (; firstComponentId < componentCount; firstComponentId++) {c = components[firstComponentId];if (c.endOffset > readerIndex) break; // 找到首个含未读数据的组件c.free();  // 释放已读组件}if (firstComponentId == 0) return this; // 无组件可丢弃// 清理缓存if (lastAccessed != null && lastAccessed.endOffset <= readerIndex) {lastAccessed = null;}removeCompRange(0, firstComponentId); // 移除已读组件// 更新元数据int offset = c.offset; // 首个保留组件的原偏移量updateComponentOffsets(0); // 重算组件偏移(从0开始)setIndex(readerIndex - offset, writerIndex - offset); // 更新读写索引adjustMarkers(offset); // 调整标记位return this;
      }

      ​核心流程​​:

      1. ​完全丢弃​​(全缓冲区已读):
        • 释放所有组件内存
        • 清空组件数组
        • 读写索引归零
      2. ​部分丢弃​​:
        • 释放头部已读组件
        • 保留首个含未读数据的组件
        • 更新组件偏移链(使保留组件的 offset=0
        • 读写索引减去丢弃的字节数

      ​索引变换示例​​:

      丢弃前:Component0: [0, 100)  已读Component1: [100, 200) 含readerIndexreaderIndex=120, writerIndex=180丢弃后:Component1新位置: [0, 100)readerIndex=20 (120-100), writerIndex=80 (180-100)

      discardReadBytes() - 丢弃已读字节​

      public CompositeByteBuf discardReadBytes() {ensureAccessible();final int readerIndex = readerIndex();if (readerIndex == 0) return this; // 无数据可丢弃int writerIndex = writerIndex();// 完全丢弃逻辑同上(略)// 部分丢弃int firstComponentId = 0;Component c = null;for (; firstComponentId < componentCount; firstComponentId++) {c = components[firstComponentId];if (c.endOffset > readerIndex) break;c.free(); // 释放完全已读的组件}// 关键区别:修改首个含未读数据的组件int trimmedBytes = readerIndex - c.offset; // 本组件内已读字节数c.offset = 0; // 组件新起始位置c.endOffset -= readerIndex; // 组件新长度c.srcAdjustment += readerIndex; // 源缓冲区索引调整c.adjustment += readerIndex; // 物理索引调整// 更新切片视图if (c.slice != null) {c.slice = c.slice.slice(trimmedBytes, c.length());}// 移除已读组件+更新元数据(同discardReadComponents)// ...return this;
      }

      ​核心点​​:

      1. ​组件内部切片​​:
        • 不丢弃整个组件,而是修改首个含未读数据的组件
        • 创建新切片:slice(trimmedBytes, c.length())
      2. ​元数据调整​​:
        c.srcAdjustment += readerIndex; // 源缓冲区起始点后移
        c.adjustment += readerIndex;    // 物理索引基准调整
      3. ​资源优化​​:
        • 避免完全释放再重建,重用底层缓冲区

      对比总结

      ​特性​discardReadComponents()discardReadBytes()
      ​组件处理​直接丢弃整个组件只丢弃组件内已读部分,重用剩余数据
      ​切片操作​修改组件的slice视图
      ​适用场景​大块数据读取后频繁读取小块数据
      ​内存优化​释放整个组件内存保留底层缓冲区,仅调整视图
      ​索引更新复杂度​高(重建偏移链)低(仅调整单个组件元数据)

      ​设计哲学​​:
      当数据读取跨越组件边界时,discardReadBytes() 通过​​原位修改组件元数据+切片视图​​,实现比discardReadComponents()更细粒度的内存回收,避免重建组件链的开销。这体现了Netty对高频小数据包处理场景的深度优化。

        边界处理范例 - 容量调整​

        public CompositeByteBuf capacity(int newCapacity) {if (newCapacity > oldCapacity) {// 扩容:添加填充缓冲区ByteBuf padding = allocBuffer(newCapacity - oldCapacity);addComponent0(false, componentCount, padding);} else if (newCapacity < oldCapacity) {// 缩容:从尾部移除组件for (int i = size-1, bytesToTrim = oldCapacity-newCapacity; i>=0; i--) {Component c = components[i];if (bytesToTrim >= c.length()) {bytesToTrim -= c.length();c.free();} else {// 部分截断c.endOffset -= bytesToTrim;break;}}}
        }
        • ​扩容​​:添加新的空组件而非重新分配大缓冲区
        • ​缩容​​:优先从尾部释放完整组件,避免数据移动

        本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
        如若转载,请注明出处:http://www.pswp.cn/bicheng/86816.shtml
        繁体地址,请注明出处:http://hk.pswp.cn/bicheng/86816.shtml

        如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

        相关文章

        用css实现文字字体颜色渐变

        用css实现文字字体颜色渐变 background-clip 是CSS3中新增的属性&#xff0c;可以用于指定背景图片或颜色的绘制范围。利用 background-clip 属性实现文字颜色从左到右、从绿到白的渐变效果&#xff1a; 代码如下&#xff1a; .gradient-color {background-image: linear-gr…

        SpringBatch处理数据性能优化

        SpringBatch的Step默认使用同步方式批量处理数据&#xff0c;也可以通过配置将读数改为同步&#xff0c;处理和写入改为异步方式。 1、同步处理Step SpringBatch的Step一般由ItemReader、ItemProcessor和ItemWriter组成&#xff0c;其中ItemProcessor是可选的。他的设计思路的…

        【机器学习深度学习】前馈神经网络(单隐藏层)

        目录 一、什么是前馈神经网络&#xff1f; 二、数学表达式是什么&#xff1f; 三、为什么需要“非线性函数”&#xff1f; 四、NumPy 实现前馈神经网络代码示例 五、 运行结果 六、代码解析 6.1 初始化部分 6.2 前向传播 6.3 计算损失&#xff08;Loss&#xff09; 6…

        设计模式系列(08):创建型模式 - 原型模式

        系列导读&#xff1a;完成创建型模式的学习&#xff0c;我们来看最后一个创建型模式——原型模式。它通过复制已有对象来创建新对象&#xff0c;是一种独特的创建方式。 解决什么问题&#xff1a;通过复制现有对象来创建新对象&#xff0c;而不是重新实例化。适用于对象创建成本…

        区块链到底是什么?

        区块链本质上是一种去中心化的分布式账本技术&#xff0c;具有以下核心特点&#xff1a; - 去中心化&#xff1a;没有中央管理机构&#xff0c;数据由网络中的多个节点共同维护&#xff0c;比如比特币网络中各个节点都保存着完整账本。 - 分布式存储&#xff1a;数据不是存在一…

        系统架构设计师论文分享-论ATAM的使用

        我的软考历程 摘要 2023年2月&#xff0c;我司通过了研发纱线MES系统的立项&#xff0c;该系统为国内纱线工厂提供SAAS服务&#xff0c;旨在提高纱线工厂的数字化和智能化水平。我在本项目中担任系统架构设计师&#xff0c;负责整个项目的架构设计工作。本文结合我在该项目中…

        vue-28(服务器端渲染(SSR)简介及其优势)

        服务器端渲染&#xff08;SSR&#xff09;简介及其优势 服务器端渲染&#xff08;SSR&#xff09;是现代网络应用的关键技术&#xff0c;特别是使用 Vue.js 等框架构建的应用。它通过在服务器上渲染初始应用状态来弥补传统单页应用&#xff08;SPA&#xff09;的局限性&#x…

        工业电子 | 什么是SerDes,为何工业和汽车应用需要它?

        重点内容速览&#xff1a; 1. 什么是SerDes&#xff1f; 2. ADI&#xff1a;私有协议的GMSL将向公有协议转变 3. TI&#xff1a;工业和汽车有两套SerDes解决方案 4. Microchip&#xff1a;推出通用协议SerDes芯片 5. 罗姆&#xff1a;主要针对汽车领域 6. 国产SerDes芯…

        大事件项目记录4-用户接口开发-更新用户基本信息

        4&#xff09;更新用户基本信息。 UserController.java&#xff1a; UserMapper.java&#xff1a; Update("update user set nickname #{nickname},email #{email},update_time #{updateTime} where id #{id}")void update(User user); UserServiceInterface…

        Transformer结构--输入编码(BPE,PE)

        在Transformer结构中&#xff0c;输入编码是模型处理文本数据的关键步骤&#xff0c;其中**BPE&#xff08;Byte Pair Encoding&#xff0c;字节对编码&#xff09;和PE&#xff08;Positional Encoding&#xff0c;位置编码&#xff09;**是两种重要的编码方式&#xff0c;它们…

        Confluence-测试用例设计指导方法

        测试经验知识库 典型的测试场景验证点各个项目有价值的经验和测试点 测试经验知识库 - 草稿测试用例执行量化指导建议 何时需要进行全量测试和如何定义和执行测试用例量的一些建议和标准 端对端&#xff08;E2E&#xff09;测试用例设计指导方案 在测试行业中&#xff0c;端到端…

        浅析JVM

        一、JVM运行流程 如图&#xff1a; JVM由四个部分构成&#xff1a; 1.类加载器 加载类文件到内存2.运行时数据区 写的程序需要加载到这里才能运行3.执行引擎 负责解释命令&#xff0c;提交操作系统执行4.本地接口 融合不同编程语言为java所用&#xff0c;如Java程序驱动打印…

        多个 Job 并发运行时共享配置文件导致上下文污染,固化 Jenkins Job 上下文

        基于 context.py 固化 Jenkins Job 上下文的完整方案&#xff0c;适用于你当前的工作流&#xff08;Python Jenkins Pipeline&#xff09;&#xff0c;解决&#xff1a; 多个 Job 并发运行时共享配置文件导致上下文污染&#xff1b;读取环境变量或 JSON 文件时被其他 Job 修改…

        简木易支付系统 功能齐全,对接接口超多

        简木易支付系统&#xff0c;作为一款引领行业潮流的卓越支付解决方案&#xff0c;依托先进的 PHP MySQL 技术架构精心打造。在开发过程中&#xff0c;它巧妙运用了功能强大的 ThinkPHP8 框架&#xff0c;完美融合前端主流技术 Vue、Element 以及 Layuiadmin&#xff0c;共同铸…

        【软考高项论文】信息系统项目的人力资源管理

        摘要 本文围绕信息系统项目的人力资源管理展开论述。以我在2024年参与的为大型国有企业构建供应链管理系统项目为例&#xff0c;阐述了项目人力资源管理的主要流程&#xff0c;包括规划、组建、建设和管理团队四个过程&#xff0c;以及所运用的工具和理论。同时&#xff0c;分…

        【EI会议征稿】东北大学主办第三届机器视觉、图像处理与影像技术国际会议(MVIPIT 2025)

        一、会议信息 大会官网&#xff1a;www.mvipit.org 官方邮箱&#xff1a;mvipit163.com 会议地点&#xff1a;辽宁沈阳 主办单位&#xff1a;东北大学 会议时间&#xff1a;2025 年 9 月 27 日-9 月 29 日 二、征稿主题 集中但不限于“机器视觉、图像处理与影像技术”等其…

        从零开始的云计算生活——第二十三天,稍作休息,Tomcat

        目录 一.故事背景 二.Tomcat概述 1、Tomcat介绍 2、Tomcat历史 二、Tomcat原理分析 1、Http工作原理 2、Tomcat整体架构 3、Coyote连接器架构 4、Catalina容器架构 5、Jasper处理流程 6、JSP编译过程 7、Tomcat启动流程 8、Tomcat请求处理流程 三、Tomcat安装与配…

        几种基于Doherty结构的GAN氮化镓功放设计方法介绍

        功率放大器是现代无线通信系统中最重要的组件之一。理想情况下&#xff0c;它们能够以高线性度和高效率提供高输出功率。但通常在这三个关键的功率放大器性能参数之间需要进行权衡取舍&#xff0c;而且具有最高输出功率和线性度的放大器往往会牺牲效率。 在支持宽带宽和高数据…

        前端打印计算单位 cm、mm、px

        A4 纵向 宽&#xff1a;21cm&#xff0c;210mm&#xff0c;793.698px 高&#xff1a;29.7cm&#xff0c;297mm&#xff0c;1122.520px A4 横向 宽&#xff1a;29.7cm&#xff0c;297mm&#xff0c;1122.520px 高&#xff1a;21cm&#xff0c;210mm&#xff0c;793.698px …

        c# sugersql 获取子表数据排序

        在C#中使用Sugar ORM&#xff08;一个流行的.NET ORM框架&#xff09;获取子表数据并进行排序&#xff0c;可以通过以下几种方式实现&#xff1a; 1. 使用HasMany或HasOne配置 首先&#xff0c;确保你在配置实体时已经正确设置了HasMany或HasOne关系。例如&#xff0c;假设你…