和catch的区别_BIO、NIO、AIO 的区别是什么?
同/異步、阻/非阻塞的區(qū)別是什么?
文件讀寫最優(yōu)雅的實現(xiàn)方式是什么?
NIO 如何實現(xiàn)多路復(fù)用功能?
帶著以上這幾個問題,跟著芒果一起進(jìn)入IO的世界吧。
在開始之前,我們先來思考一個問題:我們經(jīng)常所說的“IO”的全稱到底是什么?
可能很多人看到這個問題和我一樣一臉懵逼,IO的全稱其實是:Input/Output的縮寫。
一、IO 介紹
我們通常所說的 BIO 是相對于 NIO 來說的,BIO 也就是 Java 開始之初推出的 IO 操作模塊,BIO 是 BlockingIO 的縮寫,顧名思義就是阻塞 IO 的意思。
1.1 BIO、NIO、AIO的區(qū)別
BIO 就是傳統(tǒng)的 java.io 包,它是基于流模型實現(xiàn)的,交互的方式是同步、阻塞方式,也就是說在讀入輸入流或者輸出流時,在讀寫動作完成之前,線程會一直阻塞在那里,它們之間的調(diào)用時可靠的線性順序。它的有點就是代碼比較簡單、直觀;缺點就是 IO 的效率和擴展性很低,容易成為應(yīng)用性能瓶頸。
NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以構(gòu)建多路復(fù)用的、同步非阻塞 IO 程序,同時提供了更接近操作系統(tǒng)底層高性能的數(shù)據(jù)操作方式。
AIO 是 Java 1.7 之后引入的包,是 NIO 的升級版本,提供了異步非堵塞的 IO 操作方式,所以人們叫它 AIO(Asynchronous IO),異步 IO 是基于事件和回調(diào)機制實現(xiàn)的,也就是應(yīng)用操作之后會直接返回,不會堵塞在那里,當(dāng)后臺處理完成,操作系統(tǒng)會通知相應(yīng)的線程進(jìn)行后續(xù)的操作。
1.2 全面認(rèn)識 IO
傳統(tǒng)的 IO 大致可以分為4種類型:
InputStream、OutputStream 基于字節(jié)操作的 IO
Writer、Reader 基于字符操作的 IO
File 基于磁盤操作的 IO
Socket 基于網(wǎng)絡(luò)操作的 IO
java.net 下提供的 Scoket 很多時候人們也把它歸為 同步阻塞 IO ,因為網(wǎng)絡(luò)通訊同樣是 IO 行為。
java.io 下的類和接口很多,但大體都是 InputStream、OutputStream、Writer、Reader 的子集,所有掌握這4個類和File的使用,是用好 IO 的關(guān)鍵。
1.3 IO 使用
接下來看 InputStream、OutputStream、Writer、Reader 的繼承關(guān)系圖和使用示例。
1.3.1 InputStream 使用
繼承關(guān)系圖和類方法,如下圖:
InputStream 使用示例:
InputStream inputStream = new FileInputStream("D:\\log.txt");byte[] bytes = new byte[inputStream.available()];inputStream.read(bytes);String str = new String(bytes, "utf-8");System.out.println(str);inputStream.close();1.3.2 OutputStream 使用
繼承關(guān)系圖和類方法,如下圖:
OutputStream 使用示例:
OutputStream outputStream = new FileOutputStream("D:\\log.txt",true);// 參數(shù)二,表示是否追加,true=追加outputStream.write("你好,老王".getBytes("utf-8"));outputStream.close();1.3.3 Writer 使用
Writer 繼承關(guān)系圖和類方法,如下圖:
Writer 使用示例:
Writer writer = new FileWriter("D:\\log.txt",true); // 參數(shù)二,是否追加文件,true=追加writer.append("老王,你好");writer.close();1.3.4 Reader 使用
Reader 繼承關(guān)系圖和類方法,如下圖:
Reader 使用示例:
Reader reader = new FileReader(filePath);BufferedReader bufferedReader = new BufferedReader(reader);StringBuffer bf = new StringBuffer();String str;while ((str = bufferedReader.readLine()) != null) { bf.append(str + "\n");}bufferedReader.close();reader.close();System.out.println(bf.toString());二、同步、異步、阻塞、非阻塞
上面說了很多關(guān)于同步、異步、阻塞和非阻塞的概念,接下來就具體聊一下它們4個的含義,以及組合之后形成的性能分析。
2.1 同步與異步
同步就是一個任務(wù)的完成需要依賴另外一個任務(wù)時,只有等待被依賴的任務(wù)完成后,依賴的任務(wù)才能算完成,這是一種可靠的任務(wù)序列。要么成功都成功,失敗都失敗,兩個任務(wù)的狀態(tài)可以保持一致。而異步是不需要等待被依賴的任務(wù)完成,只是通知被依賴的任務(wù)要完成什么工作,依賴的任務(wù)也立即執(zhí)行,只要自己完成了整個任務(wù)就算完成了。至于被依賴的任務(wù)最終是否真正完成,依賴它的任務(wù)無法確定,所以它是不可靠的任務(wù)序列。我們可以用打電話和發(fā)短信來很好的比喻同步與異步操作。
2.2 阻塞與非阻塞
阻塞與非阻塞主要是從 CPU 的消耗上來說的,阻塞就是 CPU 停下來等待一個慢的操作完成 CPU 才接著完成其它的事。非阻塞就是在這個慢的操作在執(zhí)行時 CPU 去干其它別的事,等這個慢的操作完成時,CPU 再接著完成后續(xù)的操作。雖然表面上看非阻塞的方式可以明顯的提高 CPU 的利用率,但是也帶了另外一種后果就是系統(tǒng)的線程切換增加。增加的 CPU 使用時間能不能補償系統(tǒng)的切換成本需要好好評估。
2.3 同/異、阻/非堵塞 組合
同/異、阻/非堵塞的組合,有四種類型,如下表:
# 三、優(yōu)雅的文件讀寫
Java 7 之前文件的讀取是這樣的:
// 添加文件FileWriter fileWriter = new FileWriter(filePath, true);fileWriter.write(Content);fileWriter.close();// 讀取文件FileReader fileReader = new FileReader(filePath);BufferedReader bufferedReader = new BufferedReader(fileReader);StringBuffer bf = new StringBuffer();String str;while ((str = bufferedReader.readLine()) != null) { bf.append(str + "\n");}bufferedReader.close();fileReader.close();System.out.println(bf.toString());Java 7 引入了Files(java.nio包下)的,大大簡化了文件的讀寫,如下:// 寫入文件(追加方式:StandardOpenOption.APPEND)Files.write(Paths.get(filePath), Content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);// 讀取文件byte[] data = Files.readAllBytes(Paths.get(filePath));System.out.println(new String(data, StandardCharsets.UTF_8));讀寫文件都是一行代碼搞定,沒錯這就是最優(yōu)雅的文件操作。
Files 下還有很多有用的方法,比如創(chuàng)建多層文件夾,寫法上也簡單了:
// 創(chuàng)建多(單)層目錄(如果不存在創(chuàng)建,存在不會報錯)new File("D://a//b").mkdirs();
四、Socket 和 NIO 的多路復(fù)用
本節(jié)帶你實現(xiàn)最基礎(chǔ)的 Socket 的同時,同時會實現(xiàn) NIO 多路復(fù)用,還有 AIO 中 Socket 的實現(xiàn)。
4.1 傳統(tǒng)的 Socket 實現(xiàn)
接下來我們將會實現(xiàn)一個簡單的 Socket,服務(wù)器端只發(fā)給客戶端信息,再由客戶端打印出來的例子,代碼如下:
int port = 4343; //端口號// Socket 服務(wù)器端(簡單的發(fā)送信息)
Thread sThread = new Thread(new Runnable() { @Override public void run() { try { ServerSocket serverSocket = new ServerSocket(port); while (true) { // 等待連接 Socket socket = serverSocket.accept(); Thread sHandlerThread = new Thread(new Runnable() { @Override public void run() { try (PrintWriter printWriter = new PrintWriter(socket.getOutputStream())) { printWriter.println("hello world!"); printWriter.flush(); } catch (IOException e) { e.printStackTrace(); } } }); sHandlerThread.start(); } } catch (IOException e) { e.printStackTrace(); } }});sThread.start();// Socket 客戶端(接收信息并打印)try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream())); bufferedReader.lines().forEach(s -> System.out.println("客戶端:" + s));} catch (UnknownHostException e) { e.printStackTrace();} catch (IOException e) { e.printStackTrace();}
調(diào)用 accept 方法,阻塞等待客戶端連接;
利用 Socket 模擬了一個簡單的客戶端,只進(jìn)行連接、讀取和打印;
在 Java 中,線程的實現(xiàn)是比較重量級的,所以線程的啟動或者銷毀是很消耗服務(wù)器的資源的,即使使用線程池來實現(xiàn),使用上述傳統(tǒng)的 Socket 方式,當(dāng)連接數(shù)極具上升也會帶來性能瓶頸,原因是線程的上線文切換開銷會在高并發(fā)的時候體現(xiàn)的很明顯,并且以上操作方式還是同步阻塞式的編程,性能問題在高并發(fā)的時候就會體現(xiàn)的尤為明顯。
以上的流程,如下圖:
4.2 NIO 多路復(fù)用
介于以上高并發(fā)的問題,NIO 的多路復(fù)用功能就顯得意義非凡了。
NIO 是利用了單線程輪詢事件的機制,通過高效地定位就緒的 Channel,來決定做什么,僅僅 select 階段是阻塞的,可以有效避免大量客戶端連接時,頻繁線程切換帶來的問題,應(yīng)用的擴展能力有了非常大的提高。
// NIO 多路復(fù)用ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue());threadPool.execute(new Runnable() { @Override public void run() { try (Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) { serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), port)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); // 阻塞等待就緒的Channel Set selectionKeys = selector.selectedKeys(); Iterator iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); try (SocketChannel channel = ((ServerSocketChannel) key.channel()).accept()) { channel.write(Charset.defaultCharset().encode("你好,世界")); } iterator.remove(); } } } catch (IOException e) { e.printStackTrace(); } }});// Socket 客戶端(接收信息并打印)try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream())); bufferedReader.lines().forEach(s -> System.out.println("NIO 客戶端:" + s));} catch (IOException e) { e.printStackTrace();}
首先,通過 Selector.open() 創(chuàng)建一個 Selector,作為類似調(diào)度員的角色;
然后,創(chuàng)建一個 ServerSocketChannel,并且向 Selector 注冊,通過指定 SelectionKey.OP_ACCEPT,告訴調(diào)度員,它關(guān)注的是新的連接請求;
為什么我們要明確配置非阻塞模式呢?這是因為阻塞模式下,注冊操作是不允許的,會拋出 IllegalBlockingModeException 異常;
Selector 阻塞在 select 操作,當(dāng)有 Channel 發(fā)生接入請求,就會被喚醒;
下面的圖,可以有效的說明 NIO 復(fù)用的流程:
就這樣 NIO 的多路復(fù)用就大大提升了服務(wù)器端響應(yīng)高并發(fā)的能力。
4.3 AIO 版 Socket 實現(xiàn)
Java 1.7 提供了 AIO 實現(xiàn)的 Socket 是這樣的,如下代碼:
// AIO線程復(fù)用版Thread sThread = new Thread(new Runnable() { @Override public void run() { AsynchronousChannelGroup group = null; try { group = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(4)); AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(InetAddress.getLocalHost(), port)); server.accept(null, new CompletionHandler() { @Override public void completed(AsynchronousSocketChannel result, AsynchronousServerSocketChannel attachment) { server.accept(null, this); // 接收下一個請求 try { Future f = result.write(Charset.defaultCharset().encode("你好,世界")); f.get(); System.out.println("服務(wù)端發(fā)送時間:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); result.close(); } catch (InterruptedException | ExecutionException | IOException e) { e.printStackTrace(); } } @Override public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) { } }); group.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); } catch (IOException | InterruptedException e) { e.printStackTrace(); } }});sThread.start();
// Socket 客戶端
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();Future future = client.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));future.get();ByteBuffer buffer = ByteBuffer.allocate(100);client.read(buffer, null, new CompletionHandler() { @Override public void completed(Integer result, Void attachment) { System.out.println("客戶端打印:" + new String(buffer.array())); } @Override public void failed(Throwable exc, Void attachment) { exc.printStackTrace(); try { client.close(); } catch (IOException e) { e.printStackTrace(); } }});
Thread.sleep(10 * 1000);
五、總結(jié)
以上基本就是 IO 從 1.0 到目前版本(本文的版本)JDK 8 的核心使用操作了,可以看出來 IO 作為比較常用的基礎(chǔ)功能,發(fā)展變化的改動也很大,而且使用起來也越來越簡單了,IO 的操作也是比較好理解的,一個輸入一個輸出,掌握好了輸入輸出也就掌握好了 IO,Socket 作為網(wǎng)絡(luò)交互的集成功能,顯然 NIO 的多路復(fù)用,給 Socket 帶來了更多的活力和選擇,用戶可以根據(jù)自己的實際場景選擇相應(yīng)的代碼策略。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的和catch的区别_BIO、NIO、AIO 的区别是什么?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 等保2.0安全管理制度对比_一手资料!等
- 下一篇: unity自动生成敌人_Unity 3D