Netty源碼分析第五章: ByteBufhtml
概述:數組
熟悉Nio的小夥伴應該對jdk底層byteBuffer不會陌生, 也就是字節緩衝區, 主要用於對網絡底層io進行讀寫, 當channel中有數據時, 將channel中的數據讀取到字節緩衝區, 當要往對方寫數據的時候, 將字節緩衝區的數據寫到channel中網絡
可是jdk的byteBuffer是使用起來有諸多不便, 好比只有一個標記位置的指針position, 在進行讀寫操做時要頻繁的經過flip()方法進行指針位置的移動, 極易出錯, 而且byteBuffer的內存一旦分配則不能改變, 不支持動態擴容, 當讀寫的內容大於緩衝區內存時, 則會發生索引越界異常ide
而Netty的ByteBuf對jdk的byteBuffer作了從新的定義, 一樣是字節緩衝區用於讀取網絡io中的數據, 可是使用起來大大簡化, 而且支持了自動擴容, 不用擔憂讀寫數據大小超過初始分配的大小源碼分析
byteBuf根據其分類的不一樣底層實現方式有所不一樣, 有直接基於jdk底層byteBuffer實現的, 也有基於字節數組的實現的, 對於byteBuf的分類, 在後面的小節將會講到this
byteBuf中維護了兩個指針, 一是讀指針, 二是寫指針, 兩個指針相互獨立, 在讀操做的時候, 只會移動讀指針, 經過指針位置記錄讀取的字節數spa
一樣在寫操做時, 也只會移動寫指針, 經過寫指針的位置記錄寫的字節數指針
在每次讀寫操做的過程當中都會對指針的位置進行校驗, 讀指針的位置不能超過寫指針, 不然會拋出異常code
一樣, 寫指針不能超過緩衝區分配的內存, 則將對緩衝區作擴容操做orm
具體指針操做, 入下圖所示:
5-0-1
第一節: AbstractByteBuf
在講AbstractByteBuf以前, 咱們首先先了解一下ByteBuf這個類, 這是全部ByteBuf的最頂層抽象, 裏面定義了大量對ByteBuf操做的抽象方法供子類實現
AbstractByteBuf一樣也緩衝區的抽象類, 定義了byteBuf的骨架操做, 好比參數校驗, 自動擴容, 以及一些讀寫操做的指針移動, 但具體的實現, 不一樣的bytebuf實現起來是不一樣的, 這種狀況則交給其子類實現
AbstractByteBuf繼承了這個類, 並實現了其大部分的方法
首先看這個類的屬性和構造方法:
//讀指針
int readerIndex; //寫指針
int writerIndex; //保存讀指針
private int markedReaderIndex; //保存寫指針
private int markedWriterIndex; //最大分配容量
private int maxCapacity; protected AbstractByteBuf(int maxCapacity) { if (maxCapacity < 0) { throw new IllegalArgumentException("maxCapacity: " + maxCapacity + " (expected: >= 0)"); } this.maxCapacity = maxCapacity; }
咱們能夠看到在屬性中定義了讀寫指針的成員標量, 和讀寫指針位置的保存
在構造方法中能夠傳入可分配的最大內存, 而後賦值到成員變量中
咱們看幾個最簡單的方法:
@Override public int maxCapacity() { return maxCapacity; } @Override public int readerIndex() { return readerIndex; } @Override public int writerIndex() { return writerIndex; }
獲取最大內存, 獲取讀寫指針這些方法, 對全部的bytebuf都是通用的, 因此能夠定義在AbstractByteBuf中
咱們以一個writeBytes方法爲例, 讓同窗們熟悉AbstractByteBuf中哪些部分本身實現, 哪些部分則交給了子類實現:
@Override public ByteBuf writeBytes(ByteBuf src) { writeBytes(src, src.readableBytes()); return this; }
這個方法是將源的ByteBuf(參數)中的字節寫入到自身ByteBuf中
首先這裏調用了自身的writeBytes方法, 並傳入參數ByteBuf自己, 以及Bytebuf的可讀字節數, 咱們跟到readbleBytes()方法中, 其實就是調用了自身的方法:
@Override public int readableBytes() { return writerIndex - readerIndex; }
咱們看到, 這裏可讀字節數就是返回了寫指針到讀指針之間的長度
咱們再繼續跟到writeBytes(src, src.readableBytes())中:
@Override public ByteBuf writeBytes(ByteBuf src, int length) { if (length > src.readableBytes()) { throw new IndexOutOfBoundsException(String.format( "length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src)); } writeBytes(src, src.readerIndex(), length); src.readerIndex(src.readerIndex() + length); return this; }
這裏一樣調用了自身的方法首先會對參數進行驗證, 就是寫入自身的長度不能超過源ByteBuf的可讀字節數
這裏又調用了一個wirte方法, 參數傳入源Bytebuf, 其可讀字節數, 寫入的長度, 這裏寫入的長度咱們知道就是源ByteBuf的可讀字節數
咱們再跟到writeBytes(src, src.readerIndex(), length);
public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { ensureAccessible(); ensureWritable(length); setBytes(writerIndex, src, srcIndex, length); writerIndex += length; return this; }
咱們重點關注第二個校驗方法ensureWritable(length):
public ByteBuf ensureWritable(int minWritableBytes) { if (minWritableBytes < 0) { throw new IllegalArgumentException(String.format( "minWritableBytes: %d (expected: >= 0)", minWritableBytes)); } ensureWritable0(minWritableBytes); return this; }
而後咱們再跟到ensureWritable0(minWritableBytes)方法中:
private void ensureWritable0(int minWritableBytes) { if (minWritableBytes <= writableBytes()) { return; } if (minWritableBytes > maxCapacity - writerIndex) { throw new IndexOutOfBoundsException(String.format( "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", writerIndex, minWritableBytes, maxCapacity, this)); } //自動擴容
int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity); capacity(newCapacity); }
開始作了兩個參數校驗, 第一個表示當前ByteBuf寫入的長度若是要小於可寫字節數, 則返回
第二個能夠換種方式去看minWritableBytes+ writerIndex> maxCapacity 也就是須要寫入的長度+寫指針必需要小於最大分配的內存, 不然報錯, 注意這裏最大分配內存不帶表當前內存, 而是byteBuf所能分配的最大內存
若是須要寫入的長度超過了可寫字節數, 而且須要寫入的長度+寫指針不超過最大內存, 則就開始了ByteBuf很是經典也很是重要的操做, 也就是自動擴容
int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);
其中alloc()返回的是當前bytebuf返回的緩衝區分配器對象, 咱們以後的小節會講到, 這裏調用了其calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity)方法爲其擴容, 其中傳入的參數writerIndex + minWritableBytes表明所須要的容量, maxCapacity爲最大容量
咱們跟到擴容的方法裏面去:
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) { //合法性校驗
if (minNewCapacity < 0) { throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expectd: 0+)"); } if (minNewCapacity > maxCapacity) { throw new IllegalArgumentException(String.format( "minNewCapacity: %d (expected: not greater than maxCapacity(%d)", minNewCapacity, maxCapacity)); } //閾值爲4mb
final int threshold = 1048576 * 4; //最小須要擴容內存(總內存) == 閾值
if (minNewCapacity == threshold) { //返回閾值
return threshold; } //最小擴容內存>閾值
if (minNewCapacity > threshold) { //newCapacity爲須要擴容內存
int newCapacity = minNewCapacity / threshold * threshold; //目標容量+閾值>最大容量
if (newCapacity > maxCapacity - threshold) { //將最大容量做爲新容量
newCapacity = maxCapacity; } else { //不然, 目標容量+閾值
newCapacity += threshold; } return newCapacity; } //若是小於閾值
int newCapacity = 64; //目標容量<須要擴容的容量
while (newCapacity < minNewCapacity) { //倍增
newCapacity <<= 1; } //目標容量和最大容量返回一個最小的
return Math.min(newCapacity, maxCapacity); }
擴容相關的邏輯註釋也寫的很是清楚, 若是小於閾值(4mb), 採用倍增的方式, 若是大於閾值(4mb), 採用平移4mb的方式
咱們回到writeBytes(ByteBuf src, int srcIndex, int length):
public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { ensureAccessible(); ensureWritable(length); setBytes(writerIndex, src, srcIndex, length); writerIndex += length; return this; }
再往下看setBytes(writerIndex, src, srcIndex, length), 這裏的參數的意思是從當前byteBuf的writerIndex節點開始寫入, 將源緩衝區src的讀指針位置, 寫lenght個字節, 這裏的方法中AbstractByteBuf類並無提供實現, 由於不一樣類型的BtyeBuf實現的方式是不同的, 因此這裏交給了子類去實現
最後將寫指針後移length個字節
最後咱們回到writeBytes(ByteBuf src, int length)方法中:
public ByteBuf writeBytes(ByteBuf src, int length) { if (length > src.readableBytes()) { throw new IndexOutOfBoundsException(String.format( "length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src)); } writeBytes(src, src.readerIndex(), length); src.readerIndex(src.readerIndex() + length); return this; }
當writeBytes(src, src.readerIndex(), length)寫完以後, 經過src.readerIndex(src.readerIndex() + length)將源緩衝區的讀指針後移lenght個字節
以上對AbstractByteBuf的簡單介紹和其中寫操做的方法的簡單剖析