帶你完全掌握Java NIO(總結分享)
本篇文章為大家帶來了關於java的相關知識,其中主要介紹了NIO的相關問題,包括了NIO核心、BIO與NIO比較、透過NIO實現簡單的服務端客戶端通信,希望對大家有幫助。
推薦學習:《java教學》
一、Java心智圖
#二、I/O模型
I/O模型的本質是用什麼樣的通道進行資料的傳送和接收,很大程度上決定了程式通訊的效能。
Java共支援三種網路程式設計模型:BIO、NIO、AIO
BIO:同步且阻塞,服務實作模式為一個連接一個線程,即客戶端有一個連接請求時,服務端就需要啟動一個執行緒進行處理。
NIO: 同步非阻塞,伺服器實作模式為一個執行緒處理多個請求連接,即客戶端發送的請求都會註冊到多工器上,多路復用器輪詢到連接有I/O請求就進行處理。
AIO:非同步非阻塞,AIO引入非同步通道的概念,採用了Proactor模式,簡化了程式編寫,有效的請求才啟動線程,它的特點是先由作業系統完成後才通知服務端。
三、BIO、NIO、AIO應用場景
#BIO方式適用於連接數目比較小且固定的架構,這種方式對伺服器資源需求比較高, 並發侷限於應用程式中,JDK1.4以前的唯一選擇,但程式簡單易理解。
NIO方式適用於連接數目多且連接比較短(輕操作)的架構,例如聊天伺服器,彈幕 系統,伺服器間通訊等。程式比較複雜,JDK1.4開始支援。
AIO方式使用於連接數目多且連接比較長(重操作)的架構,例如相簿伺服器,充分呼叫OS參與並發操作,程式設計比較複雜,JDK7開始支援
四、BIO程式設計簡單流程
伺服器端啟動一個ServerSocket;
客戶端啟動Socket對伺服器進行通信,預設伺服器端需要對每個客戶建立一個執行緒與之通訊;
客戶端發出請求後, 先諮詢伺服器是否有執行緒回應,如果沒有則會等待,或被拒絕;
如果有回應,客戶端執行緒會等待請求結束後,在繼續執行;
五、NIO核心
NIO 有三大核心部分:Selector(選擇器)、Channel(通道)、Buffer(緩衝區)。
NIO是面向緩衝區,或者說面向區塊編程,資料讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動,這就增加了處理過程中的靈活性,使用它可以提供非阻塞式的高伸縮性網路。
HTTP2.0使用了多路復用的技術,做到同一個連線並發處理多個請求,而且並發請求 的數量比HTTP1.1大了好幾個數量級。
簡而言之,NIO可以一個執行緒處理多個請求。
六、BIO與NIO比較
BIO 以流的方式處理資料,而NIO 以區塊的方式處理資料,區塊I/O 的效率比流I /O 高很多;
BIO 是阻塞的,NIO 則是非阻塞的;
BIO基於位元組流和字元流進行操作,而NIO 是基於Channel(通道)和Buffer(緩衝區)進行操作,資料總是從通道讀取到緩衝區中,或從緩衝區寫入到通道中。 Selector(選擇器)用於監聽多個頻道的事件(例如:連線請求,資料到達等),因 此使用單一執行緒就可以監聽多個客戶端通道。
七、NIO 三大核心原理示意圖
# 流程圖說明:
Selector 對應一個線程, 一個線程對應多個channel(連接);
該圖反應了有三個channel 註冊到該selector //程式;
每個channel 都會對應一個Buffer;
#程式切換到哪個channel 是有事件決定的, Event 就是一個重要的概念;
Selector 會根據不同的事件,在各個通道上切換;
Buffer 就是一個記憶體區塊, 底層是有一個陣列;
資料的讀取寫入是透過Buffer, 這個和BIO , BIO 中要么是輸入流,或者是輸出流, 不能雙向,但是NIO的Buffer 是可以讀也可以寫, 需要flip 方法切換;
channel 是雙向的, 可以返回底層作業系統的情況, 例如Linux , 底層的作業系統通道就是雙向的;
八、緩衝區(buffer)
緩衝區本質上是一個可以讀寫資料的記憶體區塊,可以理解成是一個容器物件(含數組),該物件提供了一組方法,可以更輕鬆地使用記憶體區塊,,緩衝區物件內建了一些機制,能夠追蹤和記錄緩衝區的狀態變化。 Channel 提供從檔案、 網路讀取資料的管道,但讀取或寫入的資料都必須經由 Buffer。
在 NIO 中,Buffer 是一個頂層父類,它是一個抽象類別。
1、常用Buffer子類別一覽
ByteBuffer,儲存位元組資料到緩衝區;
-
ShortBuffer,儲存字串資料到緩衝區;
CharBuffer,儲存字元資料到緩衝區;
- ##IntBuffer,儲存整數資料到緩衝區;
- LongBuffer,儲存長整數資料到緩衝區;
- DoubleBuffer,儲存小數到緩衝區;
- FloatBuffer,儲存小數到緩衝區;
#2、buffer四大屬性
- mark:標記
- position:位置,下一個要被讀或寫的元素的索引, 每次讀寫緩衝區資料時都會改變改值, 為下次讀寫作準備。
- limit:表示緩衝區的目前終點,不能對緩衝區 超過極限的位置進行讀寫作業。且極限 是可以修改的
- capacity:容量,即可以容納的最大資料量;在緩 衝區創建時被設定並且不能改變。
3、buffer常用api
JDK1.4時,引入的api- public final int capacity( )//傳回此緩衝區的容量
- public final int position( )//傳回此緩衝區的位置
- public final Buffer position ( int newPositio)//設定此緩衝區的位置
- public final int limit( )//傳回此緩衝區的限制
- public final Buffer limit (int newLimit)//設定此緩衝區區的限制
- public final Buffer mark( )//在此緩衝區的位置設定標記
- public final Buffer reset( )//將此緩衝區的位置重設為先前標記的位置
- public final Buffer clear( )//清除此緩衝區, 即將各個標記恢復到初始狀態,但是資料並沒有真正擦除, 後面操作會覆蓋
- public final Buffer flip( )//反轉此緩衝區
- public final Buffer rewind( )//重繞此緩衝區
- public final int remaining( )//傳回目前位置與限制之間的元素數
- public final boolean hasRemaining( )//告知在目前位置和限制之間是否有元素
- public abstract boolean isReadOnly( );//告知此緩衝區是否為只告知此緩衝區是否為只讀取緩衝區
- #public abstract boolean hasArray();//告知此緩衝區是否具有可存取的底層實作陣列
- public abstract Object array();//傳回此緩衝區的底層實作陣列
- public abstract int arrayOffset();//傳回此緩衝區的底層實作陣列中第一個緩衝區元素的偏移
- public abstract boolean isDirect();//告知此緩衝區是否為直接緩衝區
- 。通道(channel)
- 1、基本介紹
(1)NIO的通道類似串流
##通道可以同時進行讀寫,而流只能讀或只能寫;通道可以實現異步讀寫數據
通道可以從緩衝讀數據,也可以寫數據到緩衝- #(2)BIO 中的stream 是單向的,例如FileInputStream 物件只能進行讀取資料的操作,而NIO 中的通道(Channel)是雙向的,可以讀取操作,也可以寫入操作。 ### (3)Channel在NIO中是一個介面### (4)常用的 Channel 類別有:FileChannel、 DatagramChannel、ServerSocketChannel 和 SocketChannel。 ServerSocketChanne 類似 ServerSocket , SocketChannel 類似 Socket。 ### (5)FileChannel 用於檔案的資料讀寫, DatagramChannel 用於 UDP 的資料讀寫, ServerSocketChannel 和 SocketChannel 用於 TCP 的資料讀寫。 #########2、FileChannel#########FileChannel主要用來對本機檔案進行IO 操作,常見的方法有:############read ,從通道讀取資料並放到緩衝區中############write,把緩衝區的資料寫到通道中############transferFrom,從目標通道中複製資料到目前通道############transferTo,把資料從目前通道複製給目標通道###
3、關於Buffer 和Channel的注意事項和細節
#ByteBuffer 支援類型化的put 和get, put 放入的是什麼資料類型,get就應該使用對應的資料類型來取出,否則可能有BufferUnderflowException 異常。
可以將一個普通Buffer 轉換成唯讀Buffer。
NIO 也提供了 MappedByteBuffer, 可以讓檔案直接在記憶體(堆外的記憶體)中進 行修改, 而如何同步到檔案由NIO 來完成。
NIO 也支援 透過多個 Buffer (即 Buffer 陣列) 完成讀寫操作,即 Scattering 和 Gathering。
十、Selector(選擇器)
1、基本介紹
Java 的NIO ,用非阻塞的IO 方式。可以用一個線程,處理多個的客戶端連 接,就會使用到Selector(選擇器)。
Selector 能夠偵測多個註冊的頻道上是否有事件發生,如果有事件發生,便取得事件然 後針對每個事件進行對應的處理。這樣就可以只用一個單線程去管理多個 通道,也就是管理多個連線和請求。
只有在連接/通道真正有讀寫事件發生時,才會進行讀寫,就大大地減少了系統開銷,並且不必為每個連接都創建一個線程,不用去維護多個線程。
避免了多執行緒之間的上下文切換所導致的開銷。
2、selector的相關方法
-
#open();//得到一個選擇器物件
select(long timeout);//監控所有註冊的通道,當其中有IO 操作可以進行時,將對應的SelectionKey 加入內部集合中並返回,參數用來設定逾時時間
selectedKeys();//從內部集合中得到所有的SelectionKey。
3、注意事項
NIO中的 ServerSocketChannel功能類似ServerSocket,SocketChannel功能類別 就像Socket。
十一、透過NIO實作簡單的服務端客戶端通訊
1、服務端
package com.nezha.guor.nio;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.*;import java.util.Iterator;public class NioServer { private Selector selector; private ServerSocketChannel serverSocketChannel; private static final int PORT = 8080; public NioServer() { try { //获得选择器 selector = Selector.open(); serverSocketChannel = ServerSocketChannel.open(); //绑定端口 serverSocketChannel.socket().bind(new InetSocketAddress(PORT)); //设置非阻塞模式 serverSocketChannel.configureBlocking(false); //将该ServerSocketChannel 注册到selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); }catch (IOException e) { System.out.println("NioServer error:"+e.getMessage()); } } public void listen() { System.out.println("监听线程启动: " + Thread.currentThread().getName()); try { while (true) { int count = selector.select(); if(count > 0) { //遍历得到selectionKey集合 Iterator<selectionkey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if(key.isAcceptable()) { SocketChannel sc = serverSocketChannel.accept(); sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); System.out.println(sc.getRemoteAddress() + " 上线 "); } //通道发送read事件,即通道是可读的状态 if(key.isReadable()) { getDataFromChannel(key); } //当前的key 删除,防止重复处理 iterator.remove(); } } else { System.out.println("等待中"); } } }catch (Exception e) { System.out.println("listen error:"+e.getMessage()); } } private void getDataFromChannel(SelectionKey key) { SocketChannel channel = null; try { channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int count = channel.read(buffer); //根据count的值做处理 if(count > 0) { String msg = new String(buffer.array()); System.out.println("来自客户端: " + msg); //向其它的客户端转发消息(排除自己) sendInfoToOtherClients(msg, channel); } }catch (IOException e) { try { System.out.println(channel.getRemoteAddress() + " 离线了"); //取消注册 key.cancel(); }catch (IOException ex) { System.out.println("getDataFromChannel error:"+ex.getMessage()); } }finally { try { channel.close(); }catch (IOException ex) { System.out.println("channel.close() error:"+ex.getMessage()); } } } //转发消息给其它客户(通道) private void sendInfoToOtherClients(String msg, SocketChannel self ) throws IOException{ System.out.println("服务器转发消息中..."); System.out.println("服务器转发数据给客户端线程: " + Thread.currentThread().getName()); //遍历 所有注册到selector 上的 SocketChannel,并排除 self for(SelectionKey key: selector.keys()) { Channel targetChannel = key.channel(); //排除自己 if(targetChannel instanceof SocketChannel && targetChannel != self) { SocketChannel dest = (SocketChannel)targetChannel; //将信息存储到buffer ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); //将buffer数据写入通道 dest.write(buffer); } } } public static void main(String[] args) { //创建服务器对象 NioServer nioServer = new NioServer(); nioServer.listen(); }}</selectionkey>
2、客戶端
package com.nezha.guor.nio;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Scanner;public class NioClient { private final int PORT = 8080; //服务器端口 private Selector selector; private SocketChannel socketChannel; private String username; public NioClient() throws IOException { selector = Selector.open(); socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT)); //设置非阻塞 socketChannel.configureBlocking(false); //将channel注册到selector socketChannel.register(selector, SelectionKey.OP_READ); username = socketChannel.getLocalAddress().toString().substring(1); System.out.println(username + " is ok..."); } //向服务器发送消息 public void sendInfo(String info) { info = username + " 说:" + info; try { socketChannel.write(ByteBuffer.wrap(info.getBytes())); }catch (IOException e) { System.out.println("sendInfo error:"+e.getMessage()); } } //读取从服务器端回复的消息 public void readInfo() { try { int readChannels = selector.select(); if(readChannels > 0) { Iterator<selectionkey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if(key.isReadable()) { //得到相关的通道 SocketChannel sc = (SocketChannel) key.channel(); //得到一个Buffer ByteBuffer buffer = ByteBuffer.allocate(1024); //读取 sc.read(buffer); //把读到的缓冲区的数据转成字符串 String msg = new String(buffer.array()); System.out.println(msg.trim()); } } iterator.remove(); //删除当前的selectionKey, 防止重复操作 } else { System.out.println("没有可以用的通道..."); } }catch (Exception e) { System.out.println("readInfo error:"+e.getMessage()); } } public static void main(String[] args) throws Exception { NioClient nioClient = new NioClient(); new Thread() { public void run() { while (true) { nioClient.readInfo(); try { Thread.currentThread().sleep(2000); }catch (InterruptedException e) { System.out.println("sleep error:"+e.getMessage()); } } } }.start(); //发送数据给服务器端 Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { nioClient.sendInfo(scanner.nextLine()); } }}</selectionkey>
3、控制台輸出
#推薦學習:《java教學》
以上是帶你完全掌握Java NIO(總結分享)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4
