Netty基於不同的使用場景,提供了幾個ByteBuf當中零拷貝的方法。這些方法和我們在NIO當中談到的不同,在NIO當中的零拷貝最終是爲了減少用戶態和內核態之間的數據拷貝。
本章節我們針對Netty提供的幾個方法進行簡單學習和使用。
一、零拷貝
先聲明一點,如果要使用以下方法的話,可能要配合前面文章介紹的retain()方法去增加引用計數,否則原ByteBuf被release()後,則會導致我們拷貝出來的buf使用失敗,導致異常。
1.1 slice
“零拷貝”的體現之一,對原始 ByteBuf 進行切片,切分成多個 ByteBuf,切片後的 ByteBuf 並沒有發生內存複製,只是多了引用到分片後的Bytebuf,然而還是使用的原始 ByteBuf 的內存,切片後的 ByteBuf 維護獨立的 read,write 指針,修改子分片,會修改原ByteBuf。
示例代碼:
public static void main(String[] args) {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});
printBuf(byteBuf);
//分片1
ByteBuf slice1 = byteBuf.slice(0, 5);
printBuf(slice1);
//分片2
ByteBuf slice2 = byteBuf.slice(5, 5);
printBuf(slice2);
//將最後一位0修改成10
slice2.setByte(4,10);
printBuf(slice2);
//打印修改後的byteBuf
printBuf(byteBuf);
}
static void printBuf(ByteBuf byteBuf){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< byteBuf.writerIndex();i++) {
stringBuilder.append(byteBuf.getByte(i));
}
System.out.println(stringBuilder);
}
結果:
1234567890
12345
67890
678910
12345678910
注意:slice後的分片,不能再次寫入新的數據,這回影響原ByteBuf。
1.2、duplicate
“零拷貝”的體現之一,拷貝了原始 ByteBuf 所有內容,長度仍然以byteBuf爲準,不能寫入新數據,也是與原始 ByteBuf 使用同一塊底層內存,只是讀寫指針是獨立的。
使用示例:
public class DuplicateTest {
public static void main(String[] args) {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});
//拷貝一塊buf
ByteBuf duplicate = byteBuf.duplicate();
printBuf(duplicate);
//將最後一位0修改成10,看一下byteBuf
duplicate.setByte(9,10);
printBuf(byteBuf);
// 寫入新數據11,看byteBuf
duplicate.writeByte(11);
printBuf(byteBuf);
}
static void printBuf(ByteBuf byteBuf){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< byteBuf.writerIndex();i++) {
stringBuilder.append(byteBuf.getByte(i));
}
System.out.println(stringBuilder);
}
}
1.3 CompositeByteBuf
“零拷貝”的體現之一,可以將多個 ByteBuf 合併爲一個邏輯上的 ByteBuf,避免拷貝。
public class CompositeByteBufTest {
public static void main(String[] args) {
ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.buffer(5);
byteBuf1.writeBytes(new byte[]{1, 2, 3, 4, 5});
ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.buffer(5);
byteBuf2.writeBytes(new byte[]{6, 7, 8, 9, 0});
CompositeByteBuf compositeByteBuf = ByteBufAllocator.DEFAULT.compositeBuffer();
// 組合兩個byteBuf,主要要使用帶有increaseWriteIndex的,否則會失敗。
compositeByteBuf.addComponents(true,byteBuf1, byteBuf2);
printBuf(compositeByteBuf);
}
static void printBuf(ByteBuf byteBuf){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< byteBuf.writerIndex();i++) {
stringBuilder.append(byteBuf.getByte(i));
}
System.out.println(stringBuilder);
}
}
1.4 Unpooled
Unpooled 是一個工具類,提供了非池化的 ByteBuf 創建、組合、複製等操作。
這裏僅介紹其跟“零拷貝”相關的 wrappedBuffer 方法
使用示例:
public static void main(String[] args) {
ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.buffer(5);
byteBuf1.writeBytes(new byte[]{1, 2, 3, 4, 5});
ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.buffer(5);
byteBuf2.writeBytes(new byte[]{6, 7, 8, 9, 0});
// CompositeByteBuf compositeByteBuf = ByteBufAllocator.DEFAULT.compositeBuffer();
// // 組合兩個byteBuf,主要要使用帶有increaseWriteIndex的,否則會失敗。
// compositeByteBuf.addComponents(true,byteBuf1, byteBuf2);
// 組合兩個byteBuf,底層使用CompositeByteBuf。
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(byteBuf1, byteBuf2);
printBuf(wrappedBuffer);
}
static void printBuf(ByteBuf byteBuf){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< byteBuf.writerIndex();i++) {
stringBuilder.append(byteBuf.getByte(i));
}
System.out.println(stringBuilder);
}
二、深度拷貝
ByteBuf提供了copy方法,這一類方法是真正的拷貝原ByteBuf到新的內存,返回一個新的ByteBuf,與原ByteBuf沒有關係。
提供兩個拷貝,一個是全量;一個指定位置和長度。
public abstract ByteBuf copy();
public abstract ByteBuf copy(int index, int length);
使用示例:
public class CopyTest {
public static void main(String[] args) {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});
ByteBuf copy1 = byteBuf.copy();
printBuf(copy1);
ByteBuf copy2 = byteBuf.copy(5, 5);
printBuf(copy2);
}
static void printBuf(ByteBuf byteBuf){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< byteBuf.writerIndex();i++) {
stringBuilder.append(byteBuf.getByte(i));
}
System.out.println(stringBuilder);
}
}
關於Bytebuf的入門就介紹這麼多了,後面會深入去探討更細節的內容。
Bytebuf簡單總結:
- 池化 - 可以重用池中 ByteBuf 實例,更節約內存,減少內存溢出的可能
- 讀寫指針分離,不需要像 ByteBuffer 一樣切換讀寫模式
- 可以自動擴容
- 支持鏈式調用,使用更流暢
- 很多地方體現零拷貝,例如 slice、duplicate、CompositeByteBuf