CompositeByteBuf 類
核心設計目標??
- ??虛擬緩沖區??:將多個
ByteBuf
合并為單一邏輯視圖,減少數據復制。 - ??零拷貝優化??:通過組合而非復制提升性能。
- ??引用計數管理??:統一管理底層
ByteBuf
的生命周期。
核心成員變量??
- ??
components
??:Component[]
數組,存儲所有組成緩沖區。 - ??
componentCount
??:當前有效組件數量(≤maxNumComponents
)。 - ??
lastAccessed
??:緩存最近訪問的Component
,加速連續訪問。 - ??
freed
??:標記緩沖區是否已釋放。
Component
Component
是 CompositeByteBuf
的內部類,??代表組合緩沖區中的一個邏輯段??,其核心職責是:
屬性/方法 | 作用 |
---|---|
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; // 解包后的底層物理緩沖區
??設計原因??:
-
??引用計數管理??:
srcBuf
是用戶直接添加的對象,負責引用計數- 釋放資源時必須釋放
srcBuf
,確保正確管理用戶提供的緩沖區生命周期
示例代碼:
ByteBuf base = Unpooled.buffer(100); // 底層物理緩沖區
ByteBuf sliced = base.slice(10, 20); // 包裝緩沖區// 添加到CompositeByteBuf時:
Component comp = new Component(srcBuf: sliced, // 用戶添加的包裝器buf: base // 解包后的物理存儲
);
雙重調整值:srcAdjustment
和 adjustment
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
為什么需要如此設計?
-
??處理任意包裝層數??:
- 用戶可能添加
SlicedByteBuf(WrappedByteBuf(PooledByteBuf))
- 需要穿透所有包裝層直達物理存儲
- 用戶可能添加
-
??生命周期分離??:
- 必須通過原始
srcBuf
釋放資源 - 但讀寫操作使用底層
buf
更高效
- 必須通過原始
-
??邏輯/物理映射??:
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)
);
解包設計要點
- ??深度解包??:確保獲取最底層數據源
- ??索引修正??:
- 累計所有切片偏移 (
adjustment
) - 保持原始讀索引 (
srcIndex
)
- 累計所有切片偏移 (
- ??字節序統一??:強制轉為
BIG_ENDIAN
- ??切片優化??:避免不必要的二次切片
通過這種設計,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) |
設計優勢??
- ??零拷貝??:邏輯聚合避免數據復制
- ??動態擴展??:組件數組按需擴容(類似
ArrayList
) - ??高效定位??:二分查找 + 訪問緩存
- ??生命周期統一??:通過
srcBuf
統一管理原始緩沖區的引用計數
核心設計亮點分析
-
??分層索引系統??
- 邏輯索引 (用戶視角) → 組件索引 → 物理索引 (底層緩沖區)
- 通過
offset/endOffset
+adjustment
實現映射
-
??寫時優化策略??
- 延遲合并:直到組件數超過閾值才觸發合并
- 懶加載:
Component.slice
延遲創建切片視圖
-
??異常安全設計??
- 資源獲取即保護:
addComponent0()
中的try-finally
確保失敗時釋放資源 - 溢出預防:所有容量計算前進行
checkForOverflow
- 資源獲取即保護:
-
??對象池應用??
RecyclableArrayList
減少臨時集合分配- 組件數組動態擴容而非固定大小
性能關鍵路徑
-
??讀取路徑優化??
用戶getByte() → 緩存檢查 → 二分查找 → 直接底層訪問
-
??寫入路徑優化??
跨組件寫入 → 自動拆分 → 邊界處理 → 只更新受影響組件
-
??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
的元數據和索引,實現了零拷貝的虛擬緩沖區視圖。其核心創新點包括:??
- ??動態組件合并??:在組件數量超過閾值時自動合并,平衡性能與內存。
- ??二分查找 + 緩存??:高效定位物理索引。
- ??生命周期統一管理??:確保底層緩沖區正確釋放。
- ??零拷貝優化??:通過
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;}
設計哲學??
- ??邏輯連續性??:通過動態維護
offset/endOffset
鏈,實現虛擬連續地址空間 - ??操作完備性??:支持任意位置的組件增刪,保持緩沖區一致性
- ??惰性優化??:僅在必要時觸發索引重排(如非尾部插入)
- ??物理隔離??:各組件保持獨立內存,避免大規模數據移動
??關鍵洞察??: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)
方法的作用是:
-
??將當前緩沖區的子區域轉換為 NIO
ByteBuffer
??- 從指定的
index
開始,截取length
長度的數據,生成一個 NIO 標準的ByteBuffer
。 - 返回的
ByteBuffer
??可能共享底層數據(零拷貝)??,也可能是數據的??獨立副本??(取決于實現)。
- 從指定的
-
??不影響原緩沖區的狀態??
- ??不修改??原緩沖區的
readerIndex
或writerIndex
。 - 修改返回的
ByteBuffer
的position
或limit
??不會影響??原緩沖區的索引或標記。
- ??不修改??原緩沖區的
-
??動態緩沖區的限制??
- 如果原緩沖區是動態的(容量可調整),后續擴容/縮容后,返回的
ByteBuffer
??不會自動同步??新內容。
- 如果原緩沖區是動態的(容量可調整),后續擴容/縮容后,返回的
-
??可能拋出異常??
- 如果底層實現不支持共享內容(如某些堆外內存或復合緩沖區),會拋出
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實現中,可能存在拷貝的情況:
- CompositeByteBuf?- 當需要將多個不連續的ByteBuf合并為單個ByteBuffer時
- 跨不同內存區域的ByteBuf?- 當堆內存和直接內存需要統一表示時
- 特定的安全要求?- 當需要防止外部修改內部數據時
性能考量:
- 通過返回多個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的根本原因:
- 組合型ByteBuf?- 由多個獨立的ByteBuf組成
- 分段存儲?- 數據分布在多個不連續的內存區域
- 性能優化?- 避免大量數據的合并拷貝,保持原有的分段結構
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);
}
??核心操作??:
- 遍歷組件范圍(
cIndex
到endCIndex-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
└───────┴───────────────┴───────┘
設計要點
- ??性能平衡??:減少組件數量提升操作效率
- ??資源管理??:
- 合并時釋放原組件資源
- 通過
free()
確保引用計數正確
- ??無縫銜接??:
- 自動維護偏移量連續性
- 不影響合并范圍外的組件
- ??內存優化??:
- 按需分配新緩沖區
- 及時清除冗余組件引用
??適用場景??:高頻讀寫操作前,減少組件數量以提升性能
??
??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;
}
??核心流程??:
- ??完全丟棄??(全緩沖區已讀):
- 釋放所有組件內存
- 清空組件數組
- 讀寫索引歸零
- ??部分丟棄??:
- 釋放頭部已讀組件
- 保留首個含未讀數據的組件
- 更新組件偏移鏈(使保留組件的
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;
}
??核心點??:
- ??組件內部切片??:
- 不丟棄整個組件,而是修改首個含未讀數據的組件
- 創建新切片:
slice(trimmedBytes, c.length())
- ??元數據調整??:
c.srcAdjustment += readerIndex; // 源緩沖區起始點后移 c.adjustment += readerIndex; // 物理索引基準調整
- ??資源優化??:
- 避免完全釋放再重建,重用底層緩沖區
對比總結
??特性?? | 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;}}}
}
- ??擴容??:添加新的空組件而非重新分配大緩沖區
- ??縮容??:優先從尾部釋放完整組件,避免數據移動