- java.nio.ByteBuffer缺點
長度固定,ByteBuffer一旦分配完成,他的容量不能動態擴展和收縮,當需要編碼的POJO對象大于ByteBuffer容量是,會發生索引越界異常
使用復雜,ByteBuffer只有一個標識位置的指針position,讀寫的時候需要手工調用flip()和rewind()等方法,使用時需要非常謹慎的使用這些api,否則很容出現錯誤
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,可以極大的簡化讀寫操作
- 讀寫操作
- 初始化
- 寫入N個字節以后
- 寫入N個字節,讀取M個字節以后
- 寫入N個字節,讀取M個字節,調用discardReadBytes以后
調用discardReadBytes會發生字節數組的內存復制,所以頻繁調用會導致性能下降
-
clear以后
ByteBuf執行clear.png
- 動態擴容
- 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;
}
- 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);
}
- Mark和Reset
對緩沖區進行讀操作時,有的時候我們需要對之前的操作進行回滾,讀操作并不會改變緩沖區的內容,回滾主要是重新設置索引信息
- 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;
}
- 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;
}
- duplicate、copy、slice、nioBuffer
duplicate:
返回當前ByteBuf的復制對象,復制后返回的ByteBuf和當前操作的ByteBuf共享緩沖區,但維護自己獨立的讀寫索引.當修改其中一個ByteBuf的內容時,另一個也會改變,即雙方持有的是同一個對象的引用copy:
復制一個新的ByteBuf對象,和原有的ByteBuf完全獨立,修改以后不會影響另外一個
3)slice:
返回當前ByteBuf的可讀子緩沖區,即從readerIndex到writerIndex的ByteBuf,返回的ByteBuf和原有緩沖區共享內容,但是維護獨立的索引.當修改其中一個ByteBuf的內容時,另一個也會改變,即雙方持有的是同一個對象的引用
- ByteBuf的類結構
常見類:
AbstractByteBuf:ByteBuf的公共屬性和功能都會在AbstractByteBuf中實現
AbstractReferenceCountedByteBuf:主要是對引用進行計數,類似于JVM內存回收的對象引用計數器,用于跟蹤對象的分配和銷毀,作自動內存回收
UnpooledHeapByteBuf:UnpooledHeapByteBuf是基于堆內存進行內存分配的字節緩沖區,沒有使用基于對象池計數實現,所以每次I/O的讀寫都會創建一個新的UnpooledHeapByteBuf.注意,頻繁的進行大塊內存的分配和回收對性能會造成一定影響
相比于PooledHeapByteBuf,UnpooledHeapByteBuf的實現更加簡單,也不容易出現內存管理的問題,所以才性能滿足的情況下,推薦使用UnpooledHeapByteBuf
HeapByteBuf(堆內存字節緩沖區):內存的分配和回收速度快,可以被JVM自動回收,缺點是如果進行socket的I/O讀寫,需要額外做一次內存復制,將堆內存對應的字節緩沖區復制到內核Channel中,性能會有一定的下降
DirectByteBuf(直接內存字節緩沖區):非堆內存,它在堆外進行內存分配,相比于堆內存,它的分配和回收速度會慢一些,但是將他寫入或者從SocketChannel中讀取出是,由于少了一次內存復制,速度比堆內存快
在I/O通信線程的讀寫緩沖區中使用DirectByteBuf,后端業務消息的編碼使用HeapByteBuf,這樣的組合性能最優