[Netty源碼分析]ByteBuf(一)

  1. java.nio.ByteBuffer缺點
  1. 長度固定,ByteBuffer一旦分配完成,他的容量不能動態擴展和收縮,當需要編碼的POJO對象大于ByteBuffer容量是,會發生索引越界異常

  2. 使用復雜,ByteBuffer只有一個標識位置的指針position,讀寫的時候需要手工調用flip()和rewind()等方法,使用時需要非常謹慎的使用這些api,否則很容出現錯誤

  3. API功能有限,一些高級、實用的特性,ByteBuffer不支持,需要開發者自己編程實現

ByteBuf通過兩個指針協助讀寫操作,讀操作使用readerIndex,寫操作使用writerIndex.

readerIndex、writerIndex初始值是0,寫入數據時writerIndex增加,讀取數據時readerIndex增加,但是readerIndex不會超過writerIndex.

讀取之后,0-readerIndex之間的空間視為discard的,調用discardReadByte方法可以釋放這一部分空間,作用類似于ByteBuffer的compact方法.readerIndex-writerIndex之間的數據是可讀的,等價于ByteBuffer中position-limit之間的數據.

writerIndex-capacity之間的空間是可寫的,等價于ByteBuffer中limit-capacity之間的空間.

讀只影響readerIndex、寫只影響writerIndex,讀寫之間不需要調整指針位置,所以相較于NIO的ByteBuffer,可以極大的簡化讀寫操作

  1. 讀寫操作
  1. 初始化
ByteBuf讀寫初始化.png
  1. 寫入N個字節以后
ByteBuf寫入N個字節.png
  1. 寫入N個字節,讀取M個字節以后
ByteBuf寫入N個字節,讀取M個字節.png
  1. 寫入N個字節,讀取M個字節,調用discardReadBytes以后
ByteBuf寫入N個字節,讀取M個字節,調用discardReadBytes.png

調用discardReadBytes會發生字節數組的內存復制,所以頻繁調用會導致性能下降

  1. clear以后


    ByteBuf執行clear.png
  1. 動態擴容
  1. NIO的ByteBuffer
寫數據時,如果buffer的空間不足,會拋出BufferOverflowException

public ByteBuffer put(byte[] src, int offset, int length) {
  checkBounds(offset, length, src.length);
  if (length > remaining())
      throw new BufferOverflowException();
  int end = offset + length;
  for (int i = offset; i < end; i++)
      this.put(src[i]);
  return this;
}
  1. Netty的ByteBuf

ByteBuf對write操作進行了封裝,有ByteBuf的write操作負責進行剩余咳喲好難過空間的校驗,如果可用緩沖區不足,ByteBuf會自動進行動態擴展。對于使用者而言不需要關心底層的校驗和擴展細節,只需要不超過capacity即可

    public ByteBuf writeBytes(byte[] src){
        writeBytes(src, 0, src.length);
        return this;
    }

    public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
        ensureWritable(length);
        setBytes(writerIndex, src, srcIndex, length);
        writerIndex += length;
        return this;
    }

    public ByteBuf ensureWritable(int minWritableBytes) {
        if (minWritableBytes < 0) {
            throw new IllegalArgumentException(
                    String.format("minWritableBytes: %d (expected: >= 0)", minWritableBytes));
        }
        ensureWritable0(minWritableBytes);
        return this;
    }

    final void ensureWritable0(int minWritableBytes) {
        ensureAccessible();
        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));
        }
        // Normalize the current capacity to the power of 2.
        int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);
        // Adjust to the new capacity.
        capacity(newCapacity);
    }
  1. Mark和Reset

對緩沖區進行讀操作時,有的時候我們需要對之前的操作進行回滾,讀操作并不會改變緩沖區的內容,回滾主要是重新設置索引信息

  1. NIO的Mark和Reset

Mark:將當前的位置指針被分到mark變量中

Reset:恢復位置指針為mark中的變量值

  public final Buffer mark() {
        mark = position;
        return this;
  }
  public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }
  1. Netty的mark和reset

ByteBuf有readerIndex、writerIndex,所以有四個相應的方法

markReaderIndex: 將當前readerIndex備份到markedReaderIndex中

resetReaderIndex: 將當前readerIndex設置為markedReaderIndex

markWriterIndex: 將當前readerIndex備份到markedWriterIndex中

resetWriterIndex: 將當前readerIndex設置為markedWriterIndex

  public ByteBuf markReaderIndex() {
      markedReaderIndex = readerIndex;
      return this;
  }

  public ByteBuf resetReaderIndex() {
      readerIndex(markedReaderIndex);
      return this;
  }

  public ByteBuf markWriterIndex() {
      markedWriterIndex = writerIndex;
      return this;
  }

  public ByteBuf resetWriterIndex() {
      writerIndex = markedWriterIndex;
      return this;
  }
  1. duplicate、copy、slice、nioBuffer
  1. duplicate:
    返回當前ByteBuf的復制對象,復制后返回的ByteBuf和當前操作的ByteBuf共享緩沖區,但維護自己獨立的讀寫索引.當修改其中一個ByteBuf的內容時,另一個也會改變,即雙方持有的是同一個對象的引用

  2. copy:
    復制一個新的ByteBuf對象,和原有的ByteBuf完全獨立,修改以后不會影響另外一個

3)slice:
返回當前ByteBuf的可讀子緩沖區,即從readerIndex到writerIndex的ByteBuf,返回的ByteBuf和原有緩沖區共享內容,但是維護獨立的索引.當修改其中一個ByteBuf的內容時,另一個也會改變,即雙方持有的是同一個對象的引用

  1. ByteBuf的類結構
ByteBuf的類結構.png

常見類:

  1. AbstractByteBuf:ByteBuf的公共屬性和功能都會在AbstractByteBuf中實現

  2. AbstractReferenceCountedByteBuf:主要是對引用進行計數,類似于JVM內存回收的對象引用計數器,用于跟蹤對象的分配和銷毀,作自動內存回收

  3. UnpooledHeapByteBuf:UnpooledHeapByteBuf是基于堆內存進行內存分配的字節緩沖區,沒有使用基于對象池計數實現,所以每次I/O的讀寫都會創建一個新的UnpooledHeapByteBuf.注意,頻繁的進行大塊內存的分配和回收對性能會造成一定影響

相比于PooledHeapByteBuf,UnpooledHeapByteBuf的實現更加簡單,也不容易出現內存管理的問題,所以才性能滿足的情況下,推薦使用UnpooledHeapByteBuf

  1. HeapByteBuf(堆內存字節緩沖區):內存的分配和回收速度快,可以被JVM自動回收,缺點是如果進行socket的I/O讀寫,需要額外做一次內存復制,將堆內存對應的字節緩沖區復制到內核Channel中,性能會有一定的下降

  2. DirectByteBuf(直接內存字節緩沖區):非堆內存,它在堆外進行內存分配,相比于堆內存,它的分配和回收速度會慢一些,但是將他寫入或者從SocketChannel中讀取出是,由于少了一次內存復制,速度比堆內存快

在I/O通信線程的讀寫緩沖區中使用DirectByteBuf,后端業務消息的編碼使用HeapByteBuf,這樣的組合性能最優

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容