日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Netty专题-(2)NIO三大核心

發布時間:2025/3/20 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Netty专题-(2)NIO三大核心 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在之前的文章Netty專題-(1)初識Netty中提到了NIO三大核心Selector 、 Channel 和 Buffer,所以在這一章重點會是介紹這三個核心。

1 緩沖區(Buffer)

1.1 基本介紹

(1)緩沖區本質上是一個可以讀寫數據的內存塊,可以理解成是一個容器對象(含數組);
(2)該對象提供了一組方法,可以更輕松地使用內存塊;
(3)緩沖區對象內置了一些機制,能夠跟蹤和記錄緩沖區的狀態變化情況;
(4)Channel 提供從文件、網絡讀取數據的渠道,但是讀取或寫入的數據都必須經由 Buffer,如圖:

1.2 Buffer類及其子類

(1)在 NIO 中,Buffer 是一個頂層父類,它是一個抽象類, 類的層級關系圖:

(2)Buffer 類定義了所有的緩沖區都具有的四個屬性來提供關于其所包含的數據元素的信息:

屬性描述
mark標記
position位置,下一個要被讀或者寫的元素的索引,每次讀寫緩存區數據時都會改變值,為下次讀寫做準備
limit表示緩沖區的當前終點,不能對緩沖區超過極限的位置進行讀寫操作,且極限是可以修改的
capacity容量,即可以容納的最大數據量,在緩沖區創建時被設定并且不能改變

(3)Buffer 類相關方法一覽

1.2 ByteBuffer
從前面可以看出對于 Java 中的基本數據類型(boolean 除外),都有一個 Buffer 類型與之相對應,最常用的是 ByteBuffer 類(二進制數據),該類的主要方法如下:

2 通道(Channel)

2.1 基本介紹

(1)NIO 的通道類似于流,但有些區別如下:

  • 通道可以同時進行讀寫,而流只能讀或者只能寫
  • 通道可以實現異步讀寫數據
  • 通道可以從緩沖讀數據,也可以寫數據到緩沖

(2)BIO 中的 stream 是單向的,例如 FileInputStream 對象只能進行讀取數據的操作,而 NIO 中的通道(Channel) 是雙向的,可以讀操作,也可以寫操作
(3)Channel 在 NIO 中是一個接口 public interface Channel extends Closeable{}

(4)常 用 的 Channel 類 有 : FileChannel 、 DatagramChannel 、 ServerSocketChannel 和 SocketChannel 。ServerSocketChanne 類似 ServerSocket , SocketChannel 類似 Socket
(5)FileChannel 用于文件的數據讀寫,DatagramChannel 用于 UDP 的數據讀寫,ServerSocketChannel 和 SocketChannel 用于 TCP 的數據讀寫。

2.2 FileChannel 類

2.2.1 FileChannel 類主要方法

FileChannel 主要用來對本地文件進行 IO 操作,常見的方法有:

  • public int read(ByteBuffer dst) ,從通道讀取數據并放到緩沖區中
  • public int write(ByteBuffer src) ,把緩沖區的數據寫到通道中
  • public long transferFrom(ReadableByteChannel src, long position, long count),從目標通道中復制數據到當前通道
  • public long transferTo(long position, long count, WritableByteChannel target),把數據從當前通道復制給目標通道

2.2.2 FileChannel 類應用實例

上面說了那么多可能大家只是停留在概念階段,最終我們是需要落到實踐,所以直接上干活,使用上面的兩個核心類來進行相應的操作。
(1)使用ByteBuffer(緩沖) 和 FileChannel(通道)實現本地文件寫數據,將 “hello,likangmin” 寫入到 file01.txt 文件中,如果文件不存在就創建。代碼里面有相應的注釋,大家應該很容易就可以看懂,我這里就不做另外的補充。大家在看的同時希望大家自己動手實踐一遍加深印象,只看不動手的話很快就會忘記。
代碼如下,很簡單的幾句:

String str = "hello,likangmin";//創建一個輸出流->channelFileOutputStream fileOutputStream = new FileOutputStream("D:\\file01.txt");//通過 fileOutputStream 獲取 對應的 FileChannel//這個 fileChannel 真實 類型是 FileChannelImplFileChannel fileChannel = fileOutputStream.getChannel();//創建一個緩沖區 ByteBufferByteBuffer byteBuffer = ByteBuffer.allocate(1024);//將 str 放入 byteBufferbyteBuffer.put(str.getBytes());//對byteBuffer 進行flipbyteBuffer.flip();//將byteBuffer 數據寫入到 fileChannelfileChannel.write(byteBuffer);fileOutputStream.close();

啟動程序以后文件寫入:

(2)使用ByteBuffer(緩沖) 和 FileChannel(通道)實現本地文件讀數據,將剛剛創建的 file01.txt 中的數據讀入到程序,并顯示在控制臺。
代碼如下:

//創建文件的輸入流File file = new File("d:\\file01.txt");FileInputStream fileInputStream = new FileInputStream(file);//通過fileInputStream 獲取對應的FileChannel -> 實際類型 FileChannelImplFileChannel fileChannel = fileInputStream.getChannel();//創建緩沖區ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());//將 通道的數據讀入到BufferfileChannel.read(byteBuffer);//將byteBuffer 的 字節數據 轉成StringSystem.out.println(new String(byteBuffer.array()));fileInputStream.close();

運行之后在控制臺打印:

(3)在上面的例子中,我們需要單獨使用兩個Buffer來進行操作,那么可以使用一個Buffer來進行文件的讀取和寫入呢?當然是可以的,其主要的實現思路圖如下,使用 FileChannel(通道) 和 方法 read , write,完成文件從1.txt到2.txt的拷貝。

代碼如下:

FileInputStream fileInputStream = new FileInputStream("1.txt");FileChannel fileChannel01 = fileInputStream.getChannel();FileOutputStream fileOutputStream = new FileOutputStream("2.txt");FileChannel fileChannel02 = fileOutputStream.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(512);while (true) { //循環讀取//這里有一個重要的操作,一定不要忘了/*public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}*/byteBuffer.clear(); //清空bufferint read = fileChannel01.read(byteBuffer);System.out.println("read =" + read);if(read == -1) { //表示讀完break;}//將buffer 中的數據寫入到 fileChannel02 -- 2.txtbyteBuffer.flip();fileChannel02.write(byteBuffer);}//關閉相關的流fileInputStream.close();fileOutputStream.close();}

文件1.txt的內容:

在開始之前2.txt不存在,啟動程序以后,2.txt出現,里面的內容與1.txt完全一致。

(4)在例(3)中,使用Buffer完成文件的拷貝,實際上我們呢可以直接使用transferFrom方法完成拷貝,比如我們拷貝以下的這張照片。

代碼如下:

//創建相關流FileInputStream fileInputStream = new FileInputStream("a.jpg");FileOutputStream fileOutputStream = new FileOutputStream("a2.jpg");//獲取各個流對應的filechannelFileChannel sourceCh = fileInputStream.getChannel();FileChannel destCh = fileOutputStream.getChannel();//使用transferForm完成拷貝destCh.transferFrom(sourceCh,0,sourceCh.size());//關閉相關通道和流sourceCh.close();destCh.close();fileInputStream.close();fileOutputStream.close();

運行完程序以后,出現a2.jpg:

2.2.3 Buffer 和 Channel 的注意事項和細節

(1)ByteBuffer 支持類型化的 put 和 get, put 放入的是什么數據類型,get 就應該使用相應的數據類型來取出,否 則可能有 BufferUnderflowException 異常。如下:

如果沒有按照對應的數據類型進行獲取,則會出現異常。

(2)可以將一個普通 Buffer 轉成只讀 Buffer

運行程序:

(3)NIO 還提供了 MappedByteBuffer, 可以讓文件直接在內存(堆外的內存)中進行修改, 而如何同步到文件 由 NIO 來完成

這里對以下主要的地方做一下說明:

MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5); 參數1: FileChannel.MapMode.READ_WRITE 使用的讀寫模式 參數20 : 可以直接修改的起始位置 參數3: 5: 是映射到內存的大小(不是索引位置) ,即將 1.txt 的多少個字節映射到內存 可以直接修改的范圍就是 0-5 實際類型 DirectByteBuffe

所以上面使用mappedByteBuffer.put(5, (byte) ‘Y’)會報IndexOutOfBoundsException異常:

(4)前面講的讀寫操作,都是通過一個 Buffer 完成的,NIO 還支持 通過多個 Buffer (即 Buffer 數組) 完成讀 寫操作,即 Scattering 和 Gathering

Scattering:將數據寫入到buffer時,可以采用buffer數組,依次寫入 [分散] Gathering: 從buffer讀取數據時,可以采用buffer數組,依次讀

代碼如下,這里我們首次使用了ServerSocketChannel,大家不用考慮太多,在后面也會具體介紹,這里當前了解即可。

//使用 ServerSocketChannel 和 SocketChannel 網絡ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);//綁定端口到socket ,并啟動serverSocketChannel.socket().bind(inetSocketAddress);//創建buffer數組ByteBuffer[] byteBuffers = new ByteBuffer[2];byteBuffers[0] = ByteBuffer.allocate(5);byteBuffers[1] = ByteBuffer.allocate(3);//等客戶端連接(telnet)SocketChannel socketChannel = serverSocketChannel.accept();int messageLength = 8; //假定從客戶端接收8個字節//循環的讀取while (true) {int byteRead = 0;while (byteRead < messageLength ) {long l = socketChannel.read(byteBuffers);byteRead += l; //累計讀取的字節數System.out.println("byteRead=" + byteRead);//使用流打印, 看看當前的這個buffer的position 和 limitArrays.asList(byteBuffers).stream().map(buffer -> "postion=" + buffer.position() + ", limit=" + buffer.limit()).forEach(System.out::println);}//將所有的buffer進行flipArrays.asList(byteBuffers).forEach(buffer -> buffer.flip());//將數據讀出顯示到客戶端long byteWirte = 0;while (byteWirte < messageLength) {long l = socketChannel.write(byteBuffers); //byteWirte += l;}//將所有的buffer 進行clearArrays.asList(byteBuffers).forEach(buffer-> {buffer.clear();});System.out.println("byteRead:=" + byteRead + " byteWrite=" + byteWirte + ", messagelength" + messageLength);}

用樣使用telnet進行數據的發送,發現分了三次進行接收:

3 Selector(選擇器)

3.1 基本介紹

(1)Java 的 NIO,用非阻塞的 IO 方式??梢杂靡粋€線程,處理多個的客戶端連接,就會使用到 Selector(選擇器)
(2)Selector 能夠檢測多個注冊的通道上是否有事件發生(注意:多個 Channel 以事件的方式可以注冊到同一個 Selector),如果有事件發生,便獲取事件然后針對每個事件進行相應的處理。這樣就可以只用一個單線程去管理多個通道,也就是管理多個連接和請求
(3)只有在連接通道真正有讀寫事件發生時,才會進行讀寫,就大大地減少了系統開銷,并且不必為每個連接都創建一個線程,不用去維護多個線程
(4)避免了多線程之間的上下文切換導致的開銷

3.2 Selector 示意圖和特點說明


(1)Netty 的 IO 線程 NioEventLoop 聚合了 Selector(選擇器,也叫多路復用器),可以同時并發處理成百上千個客 戶端連接
(2)當線程從某客戶端 Socket 通道進行讀寫數據時,若沒有數據可用時,該線程可以進行其他任務
(3)線程通常將非阻塞 IO 的空閑時間用于在其他通道上執行 IO 操作,所以單獨的線程可以管理多個輸入和輸出通道
(4)由于讀寫操作都是非阻塞的,這就可以充分提升 IO 線程的運行效率,避免由于頻繁 I/O 阻塞導致的線程掛起
(5)一個 I/O 線程可以并發處理 N 個客戶端連接和讀寫操作,這從根本上解決了傳統同步阻塞 I/O 一連接一線程模型,架構的性能、彈性伸縮能力和可靠性都得到了極大的提升

3.3 Selector 類相關方法

Selector 類是一個抽象類, 常用方法和說明如下:

3.4 注意事項

(1)NIO 中的 ServerSocketChannel 功能類似 ServerSocket,SocketChannel 功能類似 Socket
(2)selector 相關方法說明

  • selector.select()//阻塞
  • selector.select(1000);//阻塞 1000 毫秒,在 1000 毫秒后返回
  • selector.wakeup();//喚醒 selector
  • selector.selectNow();//不阻塞,立馬返還

在這一章中我們介紹了NIO的三大核心類,包括類的定義和概念,并做了相應的示例。下一章我們將利用NIO的三大核心實現NIO的網路編程Netty專題-(3)NIO網絡編程。

猜你感興趣
Netty專題-(1)初識Netty
Netty專題-(2)NIO三大核心
Netty專題-(3)NIO網絡編程
相關專題持續更新中,敬請期待…

更多文章請點擊:更多…

總結

以上是生活随笔為你收集整理的Netty专题-(2)NIO三大核心的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。