首頁/ 汽車/ 正文

看了就懂的NIO深入使用詳解

NIO概述

NIO介紹

傳統IO流(java。io):讀寫操作結束前,處於線性阻塞,程式碼簡單,安全,效能低NIO:支援非阻塞式程式設計,效能更有優勢,但程式碼編寫較為複雜。

概念理解

同步(synchronous):一條執行緒執行期間,其他執行緒就只能等待。非同步(asynchronous):一條執行緒在執行期間,其他執行緒無需等待。阻塞(blocking):當前任務未執行結束,會阻礙後續任務的執行。非阻塞(non-blocking):當前任務未執行結束,不會阻礙後續任務的執行。

IO流與NIO的區別

NIO是面向緩衝區,IO 面向流。NIO是非阻塞的,IO是阻塞的。NIO可以使用選擇器,IO不涉及選擇器。

NIO組成

Buffer(緩衝區,負責讀寫資料,類似火車)、Channel(通道 ,負責傳輸,類似鐵軌)Selector:(選擇器,負責排程通道,類似指揮中心)

Buffer

介紹

理解:實質相當於普通IO流中的陣列,負責資料的存和取。但是它提供了對資料的結構化訪問,可以跟蹤系統的讀、寫程序。常見分類:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer

核心屬性

capacity:代表緩衝區的最大容量。limit:代表剩餘(可存入/可讀取)數量position:代表(存入/讀取)位置mark:標記當前position的位置。四個屬性關係:mark <= position <= limit <= capacity

構造方法(以ByteBuffer為例)

static ByteBuffer allocate(int capacity)分配一個新的位元組緩衝區。static ByteBuffer allocateDirect(int capacity) 分配新的直接位元組緩衝區。static ByteBuffer wrap(byte[] array)將 byte 陣列包裝到緩衝區中。

常用方法

獲取屬性值

capacity():獲取緩衝區的最大容量。limit():獲取剩餘(可存入/可讀取)數量position():獲取(存入/讀取)位置mark():標記當前position的位置。

存取資料

put(Xxx[] xxx) 存入資料到緩衝區中,position >= limit不可寫。get() 獲取緩衝區的position位置資料,並將position後移,position >= limit不可讀。

核心方法

flip()翻轉此緩衝區(limit=capacity-postion,postion=0),清除標記,用於讀取模式。clear()清除此緩衝區(limit=capacity,postion=0),清除標記,用於寫入模式。rewind() 倒回這個緩衝區(position=0),清除標記。reset() 將此緩衝區的位置重置為先前標記的位置(position=mark)。

演示程式碼

public class Test01Buffer { public static void main(String[] args) { //新建緩衝區物件(預設為寫入模式) ByteBuffer b = ByteBuffer。allocate(10); //寫入資料 System。out。println(“====寫入模式屬性狀態====”); showProperty(b); System。out。println(“====讀取模式屬性狀態====”); //切換為讀取模式 b。flip(); showProperty(b); System。out。println(“====寫入資料====”); b。clear(); b。put(new byte[]{1,2}); showProperty(b); System。out。println(“====讀取資料====”); b。flip(); System。out。println(“position————>” + b。position() + “,get:” + b。get()); //迴圈遍歷通用格式 //while (b。position()” + b。position() + “,get:” + b。get()); //} System。out。println(“====重置操作位置前:記錄位置====”); showProperty(b); //記錄位置 b。mark(); System。out。println(“====重置操作位置前:獲取新資料====”); System。out。println(“position————>” + b。position() + “,get:” + b。get()); showProperty(b); System。out。println(“====重置操作位置後====”); b。reset(); showProperty(b); System。out。println(“====倒回緩衝區前====”); showProperty(b); System。out。println(“====倒回緩衝區後====”); b。rewind(); showProperty(b); } //展示引數 public static void showProperty(ByteBuffer b) { //容量 System。out。println(“capacity:” + b。capacity()); //可存放個數 System。out。println(“limit:” + b。limit()); //下一個存入位置 System。out。println(“position:” + b。position()); }}

Channel入門

介紹

理解 Channel理解為通道,包含了寫入和讀取的操作,可以理解為IO中的流物件。Channel負責讀寫,Buffer負責存取。常見分類:FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel

Channel與IO流區別

Channel是雙向的,既可以讀又可以寫,而IO是單向的Channel可以進行非同步的讀寫,IO是不支援非同步。Channel的讀寫必須透過buffer物件,IO透過流可以直接讀寫。

構造方法(以FileChannel為例 )

在IO流FileXXX位元組流中提供了getChannel()方法獲取FileChannel物件。FileChannel getChannel() 透過FileXXX位元組流的方法獲取物件

常用方法

int read(ByteBuffer dst):將資料讀取到緩衝區中 int write(ByteBuffer src):將資料從緩衝區中寫出到指定位置

演示程式碼

public class Test02FileChannel { public static void main(String[] args) throws IOException { FileChannel in = new FileInputStream(“D:\\image。jpg”)。getChannel(); FileChannel out = new FileOutputStream(“D:\\imageCopy。jpg”)。getChannel(); ByteBuffer b = ByteBuffer。allocate(10); int len = -1; while ((len = in。read(b)) != -1) { b。flip(); out。write(b); b。clear(); } in。close(); out。close(); }}

ChannelTCP協議程式設計

介紹

NIO中透過SocketChannel與ServerSocketChannel替代TCP協議的網路通訊程式設計

客戶端通道操作

SocketChannel 客戶端通道,用於讀寫TCP網路協議資料獲取物件 public static SocketChannelopen()連線伺服器 boolean connect(SocketAddress remote)SocketAddress是抽象類,使用其子類InetSocketAddress建立的物件。InetSocketAddress(String ip,int port)等待客戶端連線 SocketChannel accept()

服務端通道操作

ServerSocketChannel 服務端通道,用於服務端監聽TCP連接獲取物件 public static ServerSocketChannel open()繫結埠號 ServerSocketChannel bind(SocketAddress local)

伺服器程式碼

public class Test03ServerByChanner { public static void main(String[] args) throws IOException { //獲取伺服器通道物件 ServerSocketChannel serverSocket = ServerSocketChannel。open(); //繫結埠 ServerSocketChannel socket = serverSocket。bind(new InetSocketAddress(8888)); SocketChannel server = socket。accept(); //接收資料 System。out。println(“服務端開始接收資料……”); ByteBuffer buffer = ByteBuffer。allocate(1024); int len = -1; while ((len = server。read(buffer)) != -1) { //翻轉緩衝區,讀取資料 buffer。flip(); System。out。println(“server:” + new String(buffer。array())); buffer。clear(); } System。out。println(“服務端開始反饋資料……”); buffer。put(“資料收到了”。getBytes()); //翻轉緩衝區,讀取資料 buffer。flip(); //取出緩衝區資料,寫會給客戶端 server。write(buffer); server。close(); }}

客戶端程式碼

public class Test03ClientByChannel { public static void main(String[] args) throws Exception { //獲取連線物件 SocketChannel client = SocketChannel。open(); //連線伺服器 client。connect(new InetSocketAddress(“localhost”, 8888)); //傳送資料 System。out。println(“客戶端開始傳送資料……”); ByteBuffer buffer = ByteBuffer。allocate(1024); buffer。put(“伺服器,你好啊”。getBytes()); //翻轉緩衝區,讀取資料 buffer。flip(); //從緩衝區取出資料寫入通道 client。write(buffer); client。shutdownOutput(); //等待反饋 buffer。clear(); int len = -1; while ((len = client。read(buffer)) != -1) { buffer。flip(); System。out。println(“client:” + new String(buffer。array())); buffer。clear(); } //關閉客戶端 client。close(); }}

多路複用

介紹

非多路複用:伺服器端需要為每個埠的每次請求,開闢執行緒處理業務,高併發狀態下會造成系統性能下降。

看了就懂的NIO深入使用詳解

多路複用:伺服器端利用一個執行緒處理多個埠的訪問請求,節省CPU資源,提高程式執行效率,高併發狀態下有明顯優勢。

看了就懂的NIO深入使用詳解

核心知識

1。透過Selector中的open方法,獲取選擇器物件

public static Selector open():獲取Selector物件

2。透過Channel中的方法,註冊通道給選擇器

①建立通道物件,設定通道遮蔽模式

void configureBlocking(boolean block)

②將通道註冊給選擇器,並設定該通道的關注事件

SelectionKey register(Selector sel,int ops)Selector sel 要註冊的選擇器ops表示註冊的事件型別,在 SelectionKey類中提供的四種類型實現。 SelectionKey。OP_ACCEPT : 接收連線就緒事件,表示伺服器監聽到了客戶連線,伺服器可以接收這個連線了 SelectionKey。OP_CONNECT:連線就緒事件,表示客戶端和伺服器的連線已經建立成功 SelectionKey。OP_READ: 讀就緒事件,表示通道中有了可讀的資料,可以執行讀操作了 SelectionKey。OP_WRITE: 寫就緒事件,表示已經可以向通道中寫資料了注意事項 被註冊的Channel必須支援非同步模式,否則非同步NIO就無法工作,例如FileChannel(沒有非同步模式)不能被註冊到Selector。 ServerSocketChannel在註冊時,只能使用以OP_ACCEPT狀態註冊,否則丟擲異常。 SocketChannel在註冊時,不支援OP_ACCEPT狀態註冊。

3。透過Selector中的方法,獲取事件

int select():將事件存放至事件集合,返回已就緒事件個數。如果沒有新的已就緒事件,該方法將持續阻塞。Selector的Set selectedKeys():返回選擇器的已就緒事件集Set keys():返回選擇器的感興趣事件集(已註冊的事件數)。SelectionKey概述 SelectionKey 代表一個通道在Selector的註冊事件關係鍵。 當Selector通知某個傳入事件時,是透過對應 SelectionKey 進行傳遞的。 想要取消已註冊的通道事件,需要透過SelectionKey的cancel方法完成。SelectionKey中屬性: Interest set:興趣集,表示已註冊的事件集合,下一次呼叫方法,將測試是否有此事件的加入。 透過SelectionKey的 int interestOps() 方法,可以獲取當前 SelectionKey的感興趣事件。 Ready set:準備集,表示已準備就緒的事件集合。 透過SelectionKey的 int readyOps()方法,可以獲取當前 SelectionKey的準備就緒事件。 Channel:事件對應的通道。 透過SelectionKey的 SelectableChannel channel()方法,可以獲取當前 SelectionKey的表示的通道。 Selector:事件繫結的選擇器。 透過SelectionKey的 Selector selector() 方法,可以獲取當前 SelectionKey的繫結的選擇器。 Attached:事件物件的附加資訊。 透過 SelectionKey的 Object attach(Object ob)方法,將給定物件附加到此鍵。 透過 SelectionKey的 Object attachment()方法,檢索當前的附件。 透過 Channel的SelectionKey register(Selector sel,int ops,Object ob)方法,可以附件及獲取附加信SelectionKey迭代器

4。透過SelectionKey中的方法,判斷事件

isAcceptable() 是否有準備好接收新連線 isConnectable() 是否有完成連線狀態 isReadable() 是否有處於可讀取狀態 isWritable() 是否有處於可寫入狀態 isValid() 是否是有效的鍵

步驟

1。獲取選擇器物件2。建立通道物件,設定非同步,註冊到選擇器3。定義死迴圈,重複檢查是否有新事件觸發(Selector中的int select()方法)3。1。如果觸發新時間,獲取所有觸發事件集(Selector的Set selectedKeys()方法)3。2。獲取觸發事件集合的迭代器3。3。遍歷迭代器,獲取所有觸發的事件 3。3。1判斷觸發事件型別,指向相應操作 舉例 if (selectionKey。isAcceptable()) {} 3。3。2刪除已完成操作的觸發事件 (Iterator的remove()方法)

伺服器端程式碼

public class Test04ServerBySelector { public static void main(String[] args) throws IOException, InterruptedException { //獲取一個選擇器 Selector selector = Selector。open(); //建立三個伺服器通道,監聽三個埠 ServerSocketChannel serverChannel1 = ServerSocketChannel。open(); serverChannel1。bind(new InetSocketAddress(6666)); serverChannel1。configureBlocking(false); ServerSocketChannel serverChannel2 = ServerSocketChannel。open(); serverChannel2。bind(new InetSocketAddress(7777)); serverChannel2。configureBlocking(false); ServerSocketChannel serverChannel3 = ServerSocketChannel。open(); serverChannel3。bind(new InetSocketAddress(8888)); serverChannel3。configureBlocking(false); //將三個伺服器通道註冊給選擇器 serverChannel1。register(selector, SelectionKey。OP_ACCEPT); serverChannel2。register(selector, SelectionKey。OP_ACCEPT); serverChannel3。register(selector, SelectionKey。OP_ACCEPT); //迴圈監聽三個通道 while (true) { System。out。println(“————”); System。out。println(“等待客戶端連線。。。”); //獲取觸發的事件個數 int keyCount = selector。select();//阻塞式方法 System。out。println(“有一個客戶端連線成功。。。”); System。out。println(“已就緒事件個數=” + keyCount); System。out。println(“註冊通道數量=” + selector。keys()。size()); //獲取觸發事件集 Set selectionKeys = selector。selectedKeys(); System。out。println(“觸發事件數量=” + selectionKeys。size()); //獲取事件集迭代器 Iterator it = selectionKeys。iterator(); //遍歷事件集 while (it。hasNext()) { //獲取註冊鍵 SelectionKey selectionKey = it。next(); //使用選擇器完成資料讀取 if (selectionKey。isAcceptable()) { //獲取通道物件 ServerSocketChannel channel = (ServerSocketChannel) selectionKey。channel(); //獲取伺服器與客戶端的連線 SocketChannel server = channel。accept(); //設定非阻塞 server。configureBlocking(false); //註冊讀取事件 server。register(selector, selectionKey。OP_READ); //selectionKey。interestOps(selectionKey。OP_READ); } else if (selectionKey。isReadable()) { //獲取客戶端資料 ByteBuffer buffer = ByteBuffer。allocate(1024); SocketChannel server = (SocketChannel) selectionKey。channel(); server。read(buffer); buffer。flip(); String content = new String(buffer。array(), 0, buffer。limit()); System。out。println(“客戶端傳送的資料:” + content); //關閉資源 server。close(); } //刪除當前觸發事件 it。remove(); } System。out。println(“休息1秒,等待下一次操作。。。”); Thread。sleep(1000); } }}

客戶端程式碼

public class Test04ClientByChannel { public static void main(String[] args) { int[] ports = {7777, 8888, 6666}; for (int i = 0; i < ports。length; i++) { int port = ports[i]; new Thread(new Runnable() { @Override public void run() { try { //建立客戶端通道 SocketChannel client = SocketChannel。open(); //連線伺服器 client。connect(new InetSocketAddress(“localhost”, port)); //傳送資料 ByteBuffer buffer = ByteBuffer。allocate(1024); buffer。put(“你好啊,哈哈哈”。getBytes()); buffer。flip(); client。write(buffer); //關閉資源 client。close(); } catch (IOException e) { e。printStackTrace(); } } })。start(); } }}

非同步非阻塞互動(AIO)

介紹

支援非同步操作的NIO體系常見分類: AsynchronousSocketChannel 客戶端非同步通道 AsynchronousServerSocketChannel服務端非同步通道 AsynchronousFileChannel檔案非同步通道 AsynchronousDatagramChannel 資料非同步通道

CompletionHandler回撥介面

void completed(V result,A attachment);非同步操作成功被回撥。 void failed(Throwable exc,A attachment);非同步操作失敗時被回撥。

AsynchronousSocketChannel常用方法

public static AsynchronousSocketChannel open();開啟非同步伺服器套接字通道。void read(ByteBuffer dst,A attachment,CompletionHandler handler) 讀取資料。void write(ByteBuffer src,A attachment,CompletionHandler handler) 寫出資料

AsynchronousServerSocketChannel常用方法

public static AsynchronousServerSocketChannel open()開啟非同步伺服器套接字通道。AsynchronousServerSocketChannel bind(SocketAddress local,int backlog) ;繫結服務端IP地址,埠號void accept(A attachment,CompletionHandler handler) ;接收連線

伺服器端程式碼

package com。NIO。src。com。itheima;import java。io。IOException;import java。net。InetSocketAddress;import java。nio。ByteBuffer;import java。nio。channels。AsynchronousServerSocketChannel;import java。nio。channels。AsynchronousSocketChannel;import java。nio。channels。CompletionHandler;import java。util。concurrent。ExecutionException;public class Test05ServerBySynChanner { //如果為true,伺服器結束。 static boolean isOver = false; public static void main(String[] args) throws IOException, ExecutionException, InterruptedException { //獲取伺服器通道 AsynchronousServerSocketChannel serverChanner = AsynchronousServerSocketChannel。open(); //繫結埠號 serverChanner。bind(new InetSocketAddress(8888)); // 獲取伺服器與客戶端的對接 serverChanner。accept(“accept”, new CompletionHandler() { @Override public void completed(AsynchronousSocketChannel result, String attachment) { try { isOver=true; System。out。println(“接受了一個連線:” + result。getLocalAddress() 。toString()); // 給客戶端傳送資料並等待發送完成 result。write(ByteBuffer。wrap(“From Server:我是伺服器”。getBytes())) 。get(); ByteBuffer readBuffer = ByteBuffer。allocate(128); // 阻塞等待客戶端接收資料 result。read(readBuffer)。get(); System。out。println(new String(readBuffer。array())); } catch (InterruptedException e) { e。printStackTrace(); } catch (ExecutionException e) { e。printStackTrace(); } catch (IOException e) { e。printStackTrace(); } } @Override public void failed(Throwable exc, String attachment) { isOver=true; System。out。println(“連線失敗”); } }); //由於非同步執行,所以上述操作不會阻礙當前迴圈的執行。 while (true) { if (isOver) { break; } System。out。println(“服務端:先乾為敬”); } }}

客戶端程式碼

public class Test05ClientBySynChannel { public static void main(String[] args) throws IOException, ExecutionException, InterruptedException { //建立客戶端通道物件 AsynchronousSocketChannel client = AsynchronousSocketChannel。open(); //與伺服器進行連線 client。connect(new InetSocketAddress(“localhost”, 8888), “connect”, new CompletionHandler() { @Override public void completed(Void result, String attachment) { System。out。println(“連線到伺服器成功!”); try { // 給伺服器傳送資訊並等待發送完成 client。write(ByteBuffer。wrap(“From client:我是伺服器”。getBytes()))。get(); ByteBuffer readBuffer = ByteBuffer。allocate(128); // 阻塞等待接收服務端資料 client。read(readBuffer)。get(); System。out。println(new String(readBuffer。array())); } catch (InterruptedException e) { e。printStackTrace(); } catch (ExecutionException e) { e。printStackTrace(); } } @Override public void failed(Throwable exc, String attachment) { System。out。println(“連線到伺服器失敗”); } }); }}

相關文章

頂部