本章介紹java
ByteBuf數組
ByteBufHolder安全
ByteBufAllocatordom
使用這些接口分配緩衝和執行操做socket
每當你須要傳輸數據時,它必須包含一個緩衝區。Java NIO API自帶的緩衝區類是至關有限的,沒有通過優化,使用JDK的ByteBuffer操做更復雜。緩衝區是一個重要的組建,它是API的一部分。ide
Netty提供了一個強大的緩衝區實現用於表示一個字節序列,並幫助你操做原始字節或自定義的POJO。Netty的ByteBuf至關於JDK的ByteBuffer,ByteBuf的做用是在Netty中經過Channel傳輸數據。工具
5.1 Buffer API性能
Netty的緩衝API有兩個接口:優化
ByteBuf編碼
ByteBufHolder
Netty使用reference-counting(引用計數)的時候知道安全釋放Buf和其餘資源,雖然知道Netty有效的使用引用計數,這都是自動完成的。
這容許Netty使用池和其餘技巧來加快速度和保持內存利用率在正常水平,你不須要作任何事情來實現這一點,可是在開發Netty應用程序時,你應該處理數據儘快釋放池資源。
Netty緩衝API提供了幾個優點:
能夠自定義緩衝類型
經過一個內置的複合緩衝類型實現零拷貝
擴展性好,好比StringBuffer
不須要調用flip()來切換讀/寫模式
讀取和寫入索引分開
方法鏈
引用計數
Pooling(池)
5.2 ByteBuf - 字節數據容器
當須要與遠程進行交互時,須要以字節碼發送/接收數據。ByteBuf是一個很好的通過優化的數據容器,咱們能夠將字節數據有效的添加到ByteBuf中或從ByteBuf中獲取數據。
ByteBuf有2部分:一個用於讀,一個用於寫。咱們能夠按順序讀取數據,而且能夠跳到開始從新讀一遍。全部的數據操做,咱們只須要作的是調整讀取數據索引和再次開始讀操做。
5.2.1 ByteBuf如何工做?
寫入數據到ByteBuf後,寫入索引是增長的字節數量。開始讀字節後,讀取索引增長。你能夠讀取字節,直到寫入索引和讀取索引處理相同的位置,次數若繼續讀取,則會拋出IndexOutOfBoundsException。調用ByteBuf的任何方法開始讀/寫都會單獨維護讀索引和寫索引。ByteBuf的默認最大容量限制是Integer.MAX_VALUE,寫入時若超出這個值將會致使一個異常。
ByteBuf相似於一個字節數組,最大的區別是讀和寫的索引能夠用來控制對緩衝區數據的訪問。下圖顯示了一個容量爲16的ByteBuf:
使用Netty時會遇到3種不一樣類型的ByteBuf
Heap Buffer(堆緩衝區)
最經常使用的類型是ByteBuf將數據存儲在JVM的堆空間,這是經過將數據存儲在數組的實現。堆緩衝區能夠快速分配,當不使用時也能夠快速釋放。
它還提供了直接訪問數組的方法,經過ByteBuf.array()來獲取byte[]數據。
訪問非堆緩衝區ByteBuf的數組會致使UnsupportedOperationException,可使用ByteBuf.hasArray()來檢查是否支持訪問數組。
Direct Buffer(直接緩衝區)
直接緩衝區,在堆以外直接分配內存。直接緩衝區不會佔用堆空間容量,使用時應該考慮到應用程序要使用的最大內存容量以及如何限制它。
直接緩衝區在使用Socket傳遞數據時性能很好,由於若使用間接緩衝區,JVM會先將數據複製到直接緩衝區再進行傳遞;
可是直接緩衝區的缺點是在分配內存空間和釋放內存時比堆緩衝區更復雜,而Netty使用內存池來解決這樣的問題,這也是Netty使用內存池的緣由之一。
直接緩衝區不支持數組訪問數據,可是咱們能夠間接的訪問數據數組
ByteBuf directBuf = Unpooled.directBuffer(16); if(!directBuf.hasArray()){ int len = directBuf.readableBytes(); byte[] arr = new byte[len]; directBuf.getBytes(0, arr); }
訪問直接緩衝區的數據數組須要更多的編碼和更復雜的操做,建議若須要在數組訪問數據使用堆緩衝區會更好。
Composite Buffer(複合緩衝區)
複合緩衝區,咱們能夠建立多個不一樣的ByteBuf,而後提供一個這些ByteBuf組合的視圖。複合緩衝區就像一個列表,咱們能夠動態的添加和刪除其中的ByteBuf,JDK的ByteBuffer沒有這樣的功能。Netty提供了CompositeByteBuf類來處理複合緩衝區,CompositeByteBuf只是一個視圖,CompositeByteBuf.hasArray()老是返回false,由於它可能包含一些直接或間接的不一樣類型的ByteBuf。
例如,一條消息由header和body兩部分組成,將header和body組裝成一條消息發送出去,可能body相同,只是header不一樣,使用CompositeByteBuf就不用每次都從新分配一個新的緩衝區。下圖顯示CompositeByteBuf組成header和body:
若使用JDK的ByteBuffer就不能這樣簡單的實現,只能建立一個數組或建立一個新的ByteBuffer,再將內容複製到新的ByteBuffer中。下面是使用CompositeByteBuf的例子:
CompositeByteBuf compBuf = Unpooled.compositeBuffer(); ByteBuf heapBuf = Unpooled.buffer(8); ByteBuf directBuf = Unpooled.directBuffer(16); //添加ByteBuf到CompositeByteBuf compBuf.addComponents(heapBuf,directBuf); //刪除第一個ByteBuf compBuf.removeComponent(0); Iterator<ByteBuf> iter = compBuf.iterator(); while(iter.hasNext()){ System.out.println(iter.next().toString()); } //使用數組訪問數據 if(!compBuf.hasArray()){ int len = compBuf.readableBytes(); byte[] arr = new byte[len]; compBuf.getBytes(0, arr); }
CompositeByteBuf是ByteBuf的子類,咱們能夠像操做BytBuf同樣操做CompositeByteBuf。而且Netty優化套接字讀寫的操做是儘量的使用CompositeByteBuf來作的,使用CompositeByteBuf不會操做內存泄露問題。
5.3 ByteBuf的字節操做
ByteBuf提供了許多操做,容許修改其中的數據內容或只是讀取數據。ByteBuf和JDK的ByteBuffer很像,可是ByteBuf提供了更好的性能。
ByteBuf使用zero-based-indexing(從0開始的索引),第一個字節的索引是0,最後一個字節的索引是ByteBuf的capacity - 1,下面代碼是遍歷ByteBuf的全部字節:
//create a ByteBuf of capacity is 16 ByteBuf buf = Unpooled.buffer(16); //write data to buf for(int i=0;i<16;i++){ buf.writeByte(i+1); } //read data from buf for(int i=0;i<buf.capacity();i++){ System.out.println(buf.getByte(i)); }
注意經過索引訪問時不會推動讀索引和寫索引,咱們能夠經過ByteBuf的readerIndex()或writerIndex()來分別推動讀索引或寫索引。
5.3.2 順序訪問索引
ByteBuf提供兩個指針變量支付讀和寫操做,讀操做時使用readerIndex(),寫操做時使用writerIndex()。這和JDK的ByteBuffer不一樣,ByteBuffer只有一個方法來設置索引,因此須要使用flip()方法來切換讀和寫模式。
ByteBuf必定符合:0 <= readerIndex <= writerIndex <= capacity。
5.3.3 Discardable bytes廢棄字節
咱們能夠調用ByteBuf.discardReadBytes()來回收已經讀取過的字節,discardReadBytes()將丟棄從索引0到readerIndex之間的字節。調用discardReadBytes()方法後會變成以下圖:
ByteBuf.discardReadBytes()能夠用來清空ByteBuf中已讀取的數據,從而使ByteBuf有多餘的空間容納新的數據,可是discardReadBytes()可能會涉及內存複製,由於它須要移動ByteBuf中可讀的字節到開始位置,這樣的操做會影響性能,通常在須要立刻釋放內存的時候使用收益會比較大。
5.3.4 可讀字節(實際內容)
任何讀操做會增長readerIndex,若是讀取操做的參數也是一個ByteBuf而沒有指定目的索引,指定的目的緩衝區的writerIndex會一塊兒增長,沒有足夠的內容時會拋出IndexOutOfBoundException。新分配、包裝、複製的緩衝區的readerIndex的默認值都是0。下面代碼顯示了獲取全部可讀數據:
ByteBuf buf = Unpooled.buffer(16); while(buf.isReadable()){ System.out.println(buf.readByte()); }
5.3.5 可寫字節Writable bytes
任何寫的操做會增長writerIndex。若寫操做的參數也是一個ByteBuf而且沒有指定數據源索引,那麼指定緩衝區的readerIndex也會一塊兒增長。若沒有足夠的可寫字節會拋出IndexOutOfBoundException。新分配的緩衝區writerIndex的默認值是0。下面代碼顯示了隨機一個int數字來填充緩衝區,直到緩衝區空間耗盡:
Random random = new Random(); ByteBuf buf = Unpooled.buffer(16); while(buf.writableBytes() >= 4){ buf.writeInt(random.nextInt()); }
調用ByteBuf.clear()能夠設置readerIndex和writerIndex爲0,clear()不會清除緩衝區的內容,只是將兩個索引值設置爲0。請注意ByteBuf.clear()與JDK的ByteBuffer.clear()的語義不一樣。
下圖顯示了ByteBuf調用clear()以前:
下圖顯示了調用clear()以後:
和discardReadBytes()相比,clear()是便宜的,由於clear()不會複製任何內存。
5.3.7 搜索操做Search operations
各類indexOf()方法幫助你定位一個值的索引是否符合,咱們能夠用ByteBufProcessor複雜動態順序搜索實現簡單的靜態單字節搜索。
若是你想解碼可變長度的數據,如null結尾的字符串,你會發現bytesBefore(byte value)方法有用。
例如咱們寫一個集成的flash sockets的應用程序,這個應用程序使用NULL結束的內容,使用bytesBefore(byte value)方法能夠很容易的檢查數據中的空字節。沒有ByteBufProcessor的話,咱們須要本身作這些事情,使用ByteBufProcessor效率更好。
5.3.8 標準和重置Mark and reset
每一個ByteBuf有兩個標註索引,一個存儲readerIndex,一個存儲writerIndex。你能夠經過調用一個重置方法從新定位兩個索引之一,它相似於InputStream的標註和重置方法,沒有讀限制。
咱們能夠經過調用readerIndex(int readerIndex)和writerIndex(int writerIndex)移動讀索引和寫索引到指定位置,調用這兩個方法設置指定索引位置時可能拋出IndexOutOfBoundException。
5.3.9 衍生的緩衝區Derived buffers
調用duplicate()、slice()、slice(int index, int length)、order(ByteOrder endianness)會建立一個現有緩衝區的視圖。
衍生的緩衝區有獨立的readerIndex、writerIndex和標註索引。若是須要現有緩衝區的全新副本,可使用copy()或copy(int index, int length)得到。
看下面代碼:
// get a Charset of UTF-8 Charset utf8 = Charset.forName("UTF-8"); // get a ByteBuf ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); // slice ByteBuf sliced = buf.slice(0, 14); // copy ByteBuf copy = buf.copy(0, 14); // print "Netty in Action rocks!" System.out.println(buf.toString(utf8)); // print "Netty in Act" System.out.println(sliced.toString(utf8)); // print "Netty in Act" System.out.println(copy.toString(utf8));
5.3.10 讀/寫操做以及其餘一些操做
有兩種主要類型的讀寫操做:
get/set操做以索引爲基礎,在給定的索引設置或獲取字節
從當前索引開始讀寫,遞增當前的寫索引或讀索引
ByteBuf的各類讀寫方法或其餘一些檢查方法能夠看ByteBuf的源碼,這裏不贅述了。
5.4 ByteBufHolder
ByteBufHolder是一個輔助類,是一個接口,其實現類是DefaultByteBufHolder,還有一些實現了ByteBufHolder接口的其餘接口類。
ByteBufHolder的做用就是幫助更方便的訪問ByteBuf中的數據,當緩衝區沒用了後,可使用這個輔助類釋放資源。
ByteBufHolder很簡單,提供的可供訪問的方法也不多。若是你想實現一個「消息對象」有效負載存儲在ByteBuf,使用ByteBufHolder是一個好主意。
儘管Netty提供的各類緩衝區實現類已經很容易使用,但Netty依然提供了一些使用的工具類,使得建立和使用各類緩衝區更加方便。下面會介紹一些Netty中的緩衝區工具類。
5.4.1 ByteBufAllocator
Netty支持各類ByteBuf的池實現,來使Netty提供一種稱爲ByteBufAllocator成爲可能。ByteBufAllocator負責分配ByteBuf實例,ByteBufAllocator提供了各類分配不一樣ByteBuf的方法,如須要一個堆緩衝區可使用ByteBufAllocator.heapBuffer(),須要一個直接緩衝區可使用ByteBufAllocator.directBuffer(),須要一個複合緩衝區可使用ByteBufAllocator.compositeBuffer()。其餘方法的使用能夠看ByteBufAllocator源碼及註釋。
獲取ByteBufAllocator對象很容易,能夠從Channel的alloc()獲取,也能夠從ChannelHandlerContext的alloc()獲取。
ServerBootstrap b = new ServerBootstrap(); b.group(group).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { // get ByteBufAllocator instance by Channel.alloc() ByteBufAllocator alloc0 = ch.alloc(); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //get ByteBufAllocator instance by ChannelHandlerContext.alloc() ByteBufAllocator alloc1 = ctx.alloc(); ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE); } }); } });
Netty有兩種不一樣的ByteBufAllocator實現,一個實現ByteBuf實例池將分配和回收成本以及內存使用降到最低;另外一種實現是每次使用都建立一個新的ByteBuf實例。Netty默認使用PooledByteBufAllocator,咱們能夠經過ChannelConfig或經過引導設置一個不一樣的實現來改變。
Unpooled也是用來建立緩衝區的工具類,Unpooled的使用也很容易。Unpooled提供了不少方法,詳細方法及使用能夠看API文檔或Netty源碼。
//建立複合緩衝區 CompositeByteBuf compBuf = Unpooled.compositeBuffer(); //建立堆緩衝區 ByteBuf heapBuf = Unpooled.buffer(8); //建立直接緩衝區 ByteBuf directBuf = Unpooled.directBuffer(16);
5.4.3 ByteBufUtil
ByteBufUtil提供了一些靜態的方法,在操做ByteBuf時很是有用。ByteBufUtil提供了Unpooled以外的一些方法,也許最有價值的是hexDump(ByteBuf buffer)方法,這個方法返回指定ByteBuf中可讀字節的十六進制字符串,能夠用於調試程序時打印ByteBuf的內容,十六進制字符串相比字節而言對用戶更友好。