java的三種IO模型詳解(BIO、NIO、AIO)
一、BIO 阻塞式 IO(Blocking IO)
每個(gè)客戶端連接都會(huì)在一個(gè)獨(dú)立的線程中處理,并且這個(gè)線程在處理 IO 操作時(shí)會(huì)阻塞,直到操作完成。
- 每個(gè)連接都需要一個(gè)獨(dú)立的線程,連接數(shù)較多時(shí),會(huì)消耗大量的內(nèi)存和 CPU 資源
- 線程在處理 IO 操作時(shí)會(huì)阻塞
1.1、BIO 工作機(jī)制
- 客戶端通過 Socket 對(duì)象與服務(wù)端建立連接,從 Socket 中得到字節(jié)輸入流或輸出流進(jìn)行數(shù)據(jù)讀寫。
- 服務(wù)端通過
ServerSocket
注冊(cè)端口,調(diào)用accept
方法監(jiān)聽客戶端 Socket 請(qǐng)求,從 Socket 中得到字節(jié)輸入流或輸出流進(jìn)行數(shù)據(jù)讀寫。
1.2、BIO 實(shí)現(xiàn)單發(fā)單收
客戶端:
public static void main(String[] args) { Socket socket = null; try { //與服務(wù)端連接 socket = new Socket("127.0.0.1", 5000); //從 socket 管道中獲取字節(jié)輸出流 OutputStream os = socket.getOutputStream(); //將字節(jié)輸出流包裝為打印流 PrintStream ps = new PrintStream(os); //發(fā)一行數(shù)據(jù) ps.println("Hi BIO! 與服務(wù)端通信成功"); ps.flush(); } catch (IOException e) { e.printStackTrace(); } }
服務(wù)端:
public static void main(String[] args) { System.out.println("===服務(wù)端啟動(dòng)==="); ServerSocket serverSocket = null; try { //注冊(cè)端口 serverSocket = new ServerSocket(5000); //監(jiān)聽客戶端請(qǐng)求 Socket socket = serverSocket.accept(); //從 socket 管道中獲取字節(jié)輸入流 InputStream is = socket.getInputStream(); //將字節(jié)輸入流包裝為緩沖字符輸入流 BufferedReader br = new BufferedReader(new InputStreamReader(is)); String msg; //讀一行數(shù)據(jù) if ((msg = br.readLine()) != null) { System.out.println("服務(wù)端接收客戶端信息為:" + msg); } }catch (Exception e){ System.out.println(e.getMessage()); } }
1.3、BIO 實(shí)現(xiàn)多發(fā)多收
客戶端:
public static void main(String[] args) { try { Socket socket = new Socket("localhost",9988); OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream(os); Scanner scanner = new Scanner(System.in); while (true){ System.out.println("請(qǐng)輸入:"); String input = scanner.nextLine(); ps.println(input); ps.flush(); } } catch (IOException e) { e.printStackTrace(); } }
服務(wù)端:
public static void main(String[] args) { System.out.println("===服務(wù)端啟動(dòng)==="); try { ServerSocket ss = new ServerSocket(9988); Socket socket = ss.accept(); InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String message; while ((message = br.readLine()) != null){ System.out.println("服務(wù)端接收客戶端信息為:" + message); } } catch (IOException e) { e.printStackTrace(); } }
1.4、BIO 實(shí)現(xiàn)客戶端服務(wù)端多對(duì)一
服務(wù)端:
public void listen() throws IOException { ServerSocket serverSocket = null; try { log.info("服務(wù)啟動(dòng)監(jiān)聽"); serverSocket = new ServerSocket(9988); //循環(huán)接收到客戶端的連接 while (true) { Socket socket = serverSocket.accept(); //得到連接后,開啟一個(gè)線程處理連接 handleSocket(socket); } } finally { if(serverSocket != null){ serverSocket.close(); } } } private void handleSocket(Socket socket) { HandleSocket socketHandle = new HandleSocket(socket); new Thread(socketHandle).start(); }
public void run() { BufferedInputStream bufferedInputStream = null; BufferedOutputStream bufferedOutputStream = null; try { bufferedInputStream = new BufferedInputStream(socket.getInputStream()); byte[] bytes = new byte[1024]; int len ; if ((len = bufferedInputStream.read(bytes)) > -1) { String result = new String(bytes,0,len); System.out.println("本次接收到的結(jié)果:" + result); } System.out.println("回復(fù)信息給客戶端:"); bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); String outString = Thread.currentThread().getName() + "接收到了"; bufferedOutputStream.write(outString.getBytes()); bufferedOutputStream.flush(); } catch (IOException e) { System.out.println("處理異常:" + e.getMessage()); } finally { try { if (bufferedInputStream != null) { bufferedInputStream.close(); } if (bufferedOutputStream != null) { bufferedOutputStream.close(); } }catch (IOException e){ System.out.println("關(guān)閉流異常:" + e.getMessage()); } } }
客戶端:
public void start() throws IOException { Socket socket = new Socket("127.0.0.1", 8081); String msg = "Hi,This is the BioClient"; BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); byte[] bytes = msg.getBytes(); bufferedOutputStream.write(bytes); bufferedOutputStream.flush(); System.out.println("發(fā)送完畢"); BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream()); byte[] inBytes = new byte[1024]; int len; if ((len = bufferedInputStream.read(inBytes)) != -1) { String result = new String(inBytes, 0, len); System.out.println("接收到的消息="+result); } bufferedOutputStream.close(); bufferedInputStream.close(); socket.close(); }
1.5、BIO 模式下的端口轉(zhuǎn)發(fā)思想
一個(gè)客戶端的消息經(jīng)由服務(wù)端發(fā)送給所有的客戶端,實(shí)現(xiàn)群聊功能。
public class Server { // 定義一個(gè)靜態(tài)集合 public static List<Socket> allSocketOnLine = new ArrayList(); public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(9999); while (true){ Socket socket = ss.accept(); // 把登錄的客戶端socket存入到一個(gè)在線的集合中去 allSocketOnLine.add(socket); // 為當(dāng)前登錄成功的socket分配一個(gè)獨(dú)立的線程來處理 new ServerReaderThread(socket).start(); } } catch (IOException e) { e.printStackTrace(); } } } public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String msg; while ((msg = br.readLine()) != null) { // 發(fā)送給所有的在線socket sendMsgToAllClient(msg); } } catch (Exception e) { System.out.println("有人下線了"); Server.allSocketOnLine.remove(socket); } } /** * 把當(dāng)前客戶端發(fā)來的消息發(fā)送給全部的在線socket * @param msg */ private void sendMsgToAllClient(String msg) throws IOException { for (Socket sk : Server.allSocketOnLine) { PrintWriter pw = new PrintWriter(sk.getOutputStream()); pw.println(msg); pw.flush(); } } }
二、NIO 同步非阻塞式 IO(Non-blocking IO)
允許線程在等待IO操作完成期間可以繼續(xù)執(zhí)行其他任務(wù)。
2.1、NIO 3個(gè)核心組件(緩沖區(qū)、通道、選擇器)
- 緩沖區(qū)(Buffer):用于存儲(chǔ)數(shù)據(jù)的對(duì)象。數(shù)據(jù)從通道讀取到緩沖區(qū),或者從緩沖區(qū)寫入到通道。
- 通道(Channel):既可以從通道中讀取數(shù)據(jù),又可以寫數(shù)據(jù)到通道
- 選擇器(Selector):同時(shí)管理多個(gè)通道,通過注冊(cè)通道的事件(如連接就緒、讀就緒、寫就緒),使用單個(gè)線程就能處理多個(gè)通道,從而管理多個(gè)網(wǎng)絡(luò)連接,提高了效率。
2.2、NIO 主要特性
- 非阻塞I/O:允許線程在等待IO操作完成期間可以繼續(xù)執(zhí)行其他任務(wù)
- IO多路復(fù)用:通過選擇器,NIO允許多個(gè)通道共用一個(gè)線程進(jìn)行管理,減少了線程的資源消耗。
- 異步IO操作:可以在通道上注冊(cè)事件和回調(diào)函數(shù),實(shí)現(xiàn)非阻塞的IO操作
- 內(nèi)存映射文件:將文件的一部分或全部直接映射到內(nèi)存中,這樣可以像訪問內(nèi)存一樣訪問文件,提高了文件處理的效率。
- 文件鎖定:允許對(duì)文件的部分或全部進(jìn)行鎖定,從而控制對(duì)文件的并發(fā)訪問。
2.3、NIO 與 BIO 的對(duì)比
- 面向流與面向緩沖:BIO 是面向流的,每次從流中讀一個(gè)或多個(gè)字節(jié),直至讀取所有字節(jié);而 NIO 是面向緩沖區(qū)的。
- 阻塞與非阻塞:BIO 的流是阻塞的,當(dāng)一個(gè)線程調(diào)用
read()
或write()
時(shí),該線程被阻塞,直到有一些數(shù)據(jù)被讀取或數(shù)據(jù)完全寫入;而 NIO 是非阻塞的,一個(gè)線程從某通道發(fā)送請(qǐng)求讀取數(shù)據(jù),它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時(shí),就什么都不會(huì)獲取。 - 線程開銷:BIO 為每個(gè)客戶端連接創(chuàng)建一個(gè)線程,在大量并發(fā)連接的情況下會(huì)帶來巨大的線程開銷;NIO 通過選擇器實(shí)現(xiàn)
I/O多路復(fù)用
,在一個(gè)線程中處理多個(gè)通道,減少了線程開銷。
2.4、Buffer 常用子類
ByteBuffer
:用于存儲(chǔ)字節(jié)數(shù)據(jù);CharBuffer
:用于存儲(chǔ)字符數(shù)據(jù);ShortBuffer
:用于存儲(chǔ)Short類型數(shù)據(jù);IntBuffer
:用于存儲(chǔ)Int類型數(shù)據(jù);LongBuffer
:用于存儲(chǔ)Long類型數(shù)據(jù);FloatBuffer
:用于存儲(chǔ)Float類型數(shù)據(jù);DoubleBuffer
:用于存儲(chǔ)Double類型數(shù)據(jù);
2.5、Buffer 重要屬性
capacity(容量)
:表示 Buffer 所占的內(nèi)存大小,不能為負(fù)且創(chuàng)建后不能更改limit(限制)
:表示 Buffer 中可以操作數(shù)據(jù)的大小,不能為負(fù)且不能大于 capacity
寫模式下,表示最多能往 Buffer 里寫多少數(shù)據(jù),即 limit 等于 capacity
讀模式下,表示最多能讀到多少數(shù)據(jù),即已寫入的所有數(shù)據(jù)position(位置)
:表示下一個(gè)要讀取或?qū)懭氲臄?shù)據(jù)的索引
緩沖區(qū)位置不能為負(fù),且不能大于其限制
初始 position 值為 0,最大為 capacity – 1。當(dāng)一個(gè) byte、long 等數(shù)據(jù)寫到 Buffer 后, position 會(huì)向前移動(dòng)到下一個(gè)可插入數(shù)據(jù)的 Buffer 單元mark(標(biāo)記)
:表示記錄當(dāng)前 position 的位置,可通過 reset() 恢復(fù)到 mark 的位置
三、AIO 異步式 IO(Asynchronous IO)
異步式IO操作不會(huì)阻塞線程,而是交由操作系統(tǒng)處理。
完成后,操作系統(tǒng)會(huì)通知應(yīng)用程序,或者應(yīng)用程序主動(dòng)查詢完成狀態(tài)。
使線程在等待IO完成的同時(shí)可以執(zhí)行其他任務(wù),提高了系統(tǒng)的并發(fā)性能。
3.1、AIO 核心組件(異步通道、完成處理器)
1.異步通道(Asynchronous Channel):AIO 中進(jìn)行I/O操作的基礎(chǔ)設(shè)施。
AIO提供了多種異步通道:
AsynchronousSocketChannel
(異步套接字通道,支持面向連接的網(wǎng)絡(luò)通信)AsynchronousServerSocketChannel
(異步服務(wù)器套接字通道,支持異步服務(wù)器端套接字通信)AsynchronousFileChannel
(異步文件通道,支持異步文件讀寫操作)
2.完成處理器(Completion Handler):用于在I/O操作完成后處理結(jié)果的回調(diào)接口。
完成處理器包含兩個(gè)方法:
completed(V result, A attachment)
在I/O操作成功完成時(shí)調(diào)用;failed(Throwable exc, A attachment)
在I/O操作失敗時(shí)調(diào)用。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Springboot Retry組件@Recover失效問題解決方法
在使用springboot的retry模塊時(shí),你是否出現(xiàn)過@Recover注解失效的問題呢?不用擔(dān)心,這篇文章就來告訴你解決@Recover失效的辦法,需要的小伙伴可以參考一下2021-11-11使用java代碼實(shí)現(xiàn)一個(gè)月內(nèi)不再提醒,通用到期的問題
這篇文章主要介紹了使用java代碼實(shí)現(xiàn)一個(gè)月內(nèi)不再提醒,通用到期的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01spring使用aspect注解切面不起作用的排查過程及解決
這篇文章主要介紹了spring使用aspect注解切面不起作用的排查過程及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Java實(shí)現(xiàn)一鍵生成表controller,service,mapper文件
這篇文章主要為大家詳細(xì)介紹了如何利用Java語言實(shí)現(xiàn)一鍵生成表controller,service,mapper文件,文中的示例代碼講解詳細(xì),需要的可以收藏一下2023-05-05