Netty In Action中文版 - 第五章:Buffers(緩衝)

本章介紹java

  • ByteBuf
  • ByteBufHolder
  • ByteBufAllocator
  • 使用這些接口分配緩衝和運行操做
        每當你需要數據傳輸時,它必須包括一個緩衝區。Java NIO API自帶的緩衝區類是至關有限的,沒有通過優化,使用JDK的ByteBuffer操做更復雜。緩衝區是一個重要的組建,它是API的一部分。

Netty提供了一個強大的緩衝區實現用於表示一個字節序列,並幫助你操做原始字節或本身定義的POJO。Netty的ByteBuf至關於JDK的ByteBuffer,ByteBuf的做用是在Netty中經過Channel數據傳輸。它被又一次設計以解決JDK的ByteBuffer中的一些問題,從而使開發者開發網絡應用程序顯得更有效率。本章將講述Netty中的緩衝區,並瞭解它爲何比JDK自帶的緩衝區實現更優秀。還會深刻了解在Netty中使用ByteBuf訪問數據以及怎樣使用它。 數組

5.1 Buffer API

        Netty的緩衝API有兩個接口:
  • ByteBuf
  • ByteBufHolder
Netty使用reference-counting(引用計數)的時候知道安全釋放Buf和其它資源,儘管知道Netty有效的使用引用計數。這都是本身主動完畢的。這贊成Netty使用池和其它技巧來加高速度和保持內存利用率在正常水平,你不需要作不論什麼事情來實現這一點。但是在開發Netty應用程序時,你應該處理數據儘快釋放池資源。
        Netty緩衝API提供了幾個優點:
  • 可以本身定義緩衝類型
  • 經過一個內置的複合緩衝類型實現零拷貝
  • 擴展性好,比方StringBuffer
  • 不需要調用flip()來切換讀/寫模式
  • 讀取和寫入索引分開
  • 方法鏈
  • 引用計數
  • Pooling(池)

5.2 ByteBuf - 字節數據容器

        當需要與遠程進行交互時,需要以字節碼發送/接收數據。

由於各類緣由,一個高效、方便、易用的數據接口是必須的,而Netty的ByteBuf知足這些需求,ByteBuf是一個很是好的通過優化的數據容器。咱們可以將字節數據有效的加入到ByteBuf中或從ByteBuf中獲取數據。ByteBuf有2部分:一個用於讀,一個用於寫。咱們可以按順序的讀取數據,並且可以跳到開始又一次讀一遍。所有的數據操做,咱們僅僅需要作的是調整讀取數據索引和再次開始讀操做。安全

5.2.1 ByteBuf怎樣在工做?

        寫入數據到ByteBuf後,寫入索引是添加的字節數量。

開始讀字節後,讀取索引添加。你可以讀取字節,直到寫入索引和讀取索引處理一樣的位置,次數若繼續讀取。則會拋出IndexOutOfBoundsException。調用ByteBuf的不論什麼方法開始讀/寫都會單獨維護讀索引和寫索引。ByteBuf的默認最大容量限制是Integer.MAX_VALUE,寫入時若超出這個值將會致使一個異常。網絡

        ByteBuf相似於一個字節數組。最大的差異是讀和寫的索引可以用來控制對緩衝區數據的訪問。

下圖顯示了一個容量爲16的ByteBuf:dom


5.2.2 不一樣類型的ByteBuf

        使用Netty時會遇到3種不一樣類型的ByteBuf
Heap Buffer(堆緩衝區)
        最常用的類型是ByteBuf將數據存儲在JVM的堆空間,這是經過將數據存儲在數組的實現。堆緩衝區可以高速分配,當不使用時也可以高速釋放。它還提供了直接訪問數組的方法,經過ByteBuf.array()來獲取byte[]數據。
        訪問非堆緩衝區ByteBuf的數組會致使UnsupportedOperationException,可以使用ByteBuf.hasArray()來檢查是否支持訪問數組。
Direct Buffer(直接緩衝區)
        直接緩衝區,在堆以外直接分配內存。

直接緩衝區不會佔用堆空間容量,使用時應該考慮到應用程序要使用的最大內存容量以及怎樣限制它。直接緩衝區在使用Socket傳遞數據時性能很是好,因爲若使用間接緩衝區,JVM會先將數據拷貝到直接緩衝區再進行傳遞;但是直接緩衝區的缺點是在分配內存空間和釋放內存時比堆緩衝區更復雜。而Netty使用內存池來解決這種問題。這也是Netty使用內存池的緣由之中的一個。直接緩衝區不支持數組訪問數據,但是咱們可以間接的訪問數據數組,如如下代碼:socket

		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:

ide

若使用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提供了更好的性能。

5.3.1 隨機訪問索引

        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());
		}
(代碼於原書中有出入。原書多是基於Netty4以前的版本號解說的,此處基於Netty4)

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());
		}

5.3.6 清除緩衝區索引Clearing the buffer indexs

        調用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)方法可以很是easy的檢查數據中的空字節。沒有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很是easy,提供的可供訪問的方法也很是少。假設你想實現一個「消息對象」有效負載存儲在ByteBuf,使用ByteBufHolder是一個好主意。學習

        雖然Netty提供的各類緩衝區實現類已經很是easy使用,但Netty依舊提供了一些使用的工具類,使得建立和使用各類緩衝區更加方便。

如下會介紹一些Netty中的緩衝區工具類。

5.4.1 ByteBufAllocator

        Netty支持各類ByteBuf的池實現,來使Netty提供一種稱爲ByteBufAllocator成爲可能。ByteBufAllocator負責分配ByteBuf實例,ByteBufAllocator提供了各類分配不一樣ByteBuf的方法,如需要一個堆緩衝區可以使用ByteBufAllocator.heapBuffer()。需要一個直接緩衝區可以使用ByteBufAllocator.directBuffer(),需要一個複合緩衝區可以使用ByteBufAllocator.compositeBuffer()。

其它方法的使用可以看ByteBufAllocator源代碼及凝視。

        獲取ByteBufAllocator對象很是easy,可以從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或經過引導設置一個不一樣的實現來改變。不少其它細節在後面講述。

5.4.2 Unpooled

        Unpooled也是用來建立緩衝區的工具類,Unpooled的使用也很是easy。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的內容。十六進制字符串相比字節而言對用戶更友好。

5.5 Summary

        本章主要學習Netty提供的緩衝區類ByteBuf的建立和簡單有用以及一些操做ByteBuf的工具類。
相關文章
相關標籤/搜索