深入java核心_Java核心(五)深入理解BIO、NIO、AIO

導讀:本文你將獲取到:同/異步 + 阻/非阻塞的性能區別;BIO、NIO、AIO 的區別;理解和實現 NIO 操作 Socket 時的多路復用;同時掌握 IO 最底層最核心的操作技巧。

BIO、NIO、AIO 的區別是什么?

同/異步、阻/非阻塞的區別是什么?

文件讀寫最優雅的實現方式是什么?

NIO 如何實現多路復用功能?

帶著以上這幾個問題,讓我們一起進入IO的世界吧。

在開始之前,我們先來思考一個問題:我們經常所說的“IO”的全稱到底是什么?

可能很多人看到這個問題和我一樣一臉懵逼,IO的全稱其實是:Input/Output的縮寫。

一、IO 介紹

我們通常所說的 BIO 是相對于 NIO 來說的,BIO 也就是 Java 開始之初推出的 IO 操作模塊,BIO 是 BlockingIO 的縮寫,顧名思義就是阻塞 IO 的意思。

1.1 BIO、NIO、AIO的區別

BIO 就是傳統的 java.io 包,它是基于流模型實現的,交互的方式是同步、阻塞方式,也就是說在讀入輸入流或者輸出流時,在讀寫動作完成之前,線程會一直阻塞在那里,它們之間的調用時可靠的線性順序。它的有點就是代碼比較簡單、直觀;缺點就是 IO 的效率和擴展性很低,容易成為應用性能瓶頸。

NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以構建多路復用的、同步非阻塞 IO 程序,同時提供了更接近操作系統底層高性能的數據操作方式。

AIO 是 Java 1.7 之后引入的包,是 NIO 的升級版本,提供了異步非堵塞的 IO 操作方式,所以人們叫它 AIO(Asynchronous IO),異步 IO 是基于事件和回調機制實現的,也就是應用操作之后會直接返回,不會堵塞在那里,當后臺處理完成,操作系統會通知相應的線程進行后續的操作。

1.2 全面認識 IO

傳統的 IO 大致可以分為4種類型:

InputStream、OutputStream 基于字節操作的 IO

Writer、Reader 基于字符操作的 IO

File 基于磁盤操作的 IO

Socket 基于網絡操作的 IO

java.net 下提供的 Scoket 很多時候人們也把它歸為 同步阻塞 IO ,因為網絡通訊同樣是 IO 行為。

java.io 下的類和接口很多,但大體都是 InputStream、OutputStream、Writer、Reader 的子集,所有掌握這4個類和File的使用,是用好 IO 的關鍵。

1.3 IO 使用

接下來看 InputStream、OutputStream、Writer、Reader 的繼承關系圖和使用示例。

1.3.1 InputStream 使用

繼承關系圖和類方法,如下圖:

AAffA0nNPuCLAAAAAElFTkSuQmCC

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 使用

繼承關系圖和類方法,如下圖:

AAffA0nNPuCLAAAAAElFTkSuQmCC

OutputStream 使用示例:

OutputStream outputStream = new FileOutputStream("D:\\log.txt",true); // 參數二,表示是否追加,true=追加

outputStream.write("你好,老王".getBytes("utf-8"));

outputStream.close();

1.3.3 Writer 使用

Writer 繼承關系圖和類方法,如下圖:

AAffA0nNPuCLAAAAAElFTkSuQmCC

Writer 使用示例:

Writer writer = new FileWriter("D:\\log.txt",true); // 參數二,是否追加文件,true=追加

writer.append("老王,你好");

writer.close();

1.3.4 Reader 使用

Reader 繼承關系圖和類方法,如下圖:

AAffA0nNPuCLAAAAAElFTkSuQmCC

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());

二、同步、異步、阻塞、非阻塞

上面說了很多關于同步、異步、阻塞和非阻塞的概念,接下來就具體聊一下它們4個的含義,以及組合之后形成的性能分析。

2.1 同步與異步

同步就是一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成后,依賴的任務才能算完成,這是一種可靠的任務序列。要么成功都成功,失敗都失敗,兩個任務的狀態可以保持一致。而異步是不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什么工作,依賴的任務也立即執行,只要自己完成了整個任務就算完成了。至于被依賴的任務最終是否真正完成,依賴它的任務無法確定,所以它是不可靠的任務序列。我們可以用打電話和發短信來很好的比喻同步與異步操作。

2.2 阻塞與非阻塞

阻塞與非阻塞主要是從 CPU 的消耗上來說的,阻塞就是 CPU 停下來等待一個慢的操作完成 CPU 才接著完成其它的事。非阻塞就是在這個慢的操作在執行時 CPU 去干其它別的事,等這個慢的操作完成時,CPU 再接著完成后續的操作。雖然表面上看非阻塞的方式可以明顯的提高 CPU 的利用率,但是也帶了另外一種后果就是系統的線程切換增加。增加的 CPU 使用時間能不能補償系統的切換成本需要好好評估。

2.3 同/異、阻/非堵塞 組合

同/異、阻/非堵塞的組合,有四種類型,如下表:

組合方式性能分析

同步阻塞最常用的一種用法,使用也是最簡單的,但是 I/O 性能一般很差,CPU 大部分在空閑狀態。

同步非阻塞提升 I/O 性能的常用手段,就是將 I/O 的阻塞改成非阻塞方式,尤其在網絡 I/O 是長連接,同時傳輸數據也不是很多的情況下,提升性能非常有效。 這種方式通常能提升 I/O 性能,但是會增加CPU 消耗,要考慮增加的 I/O 性能能不能補償 CPU 的消耗,也就是系統的瓶頸是在 I/O 還是在 CPU 上。

異步阻塞這種方式在分布式數據庫中經常用到,例如在網一個分布式數據庫中寫一條記錄,通常會有一份是同步阻塞的記錄,而還有兩至三份是備份記錄會寫到其它機器上,這些備份記錄通常都是采用異步阻塞的方式寫 I/O。異步阻塞對網絡 I/O 能夠提升效率,尤其像上面這種同時寫多份相同數據的情況。

異步非阻塞這種組合方式用起來比較復雜,只有在一些非常復雜的分布式情況下使用,像集群之間的消息同步機制一般用這種 I/O 組合方式。如 Cassandra 的 Gossip 通信機制就是采用異步非阻塞的方式。它適合同時要傳多份相同的數據到集群中不同的機器,同時數據的傳輸量雖然不大,但是卻非常頻繁。這種網絡 I/O 用這個方式性能能達到最高。

# 三、優雅的文件讀寫

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));

讀寫文件都是一行代碼搞定,沒錯這就是最優雅的文件操作。

Files 下還有很多有用的方法,比如創建多層文件夾,寫法上也簡單了:

// 創建多(單)層目錄(如果不存在創建,存在不會報錯)

new File("D://a//b").mkdirs();

四、Socket 和 NIO 的多路復用

本節帶你實現最基礎的 Socket 的同時,同時會實現 NIO 多路復用,還有 AIO 中 Socket 的實現。

4.1 傳統的 Socket 實現

接下來我們將會實現一個簡單的 Socket,服務器端只發給客戶端信息,再由客戶端打印出來的例子,代碼如下:

int port = 4343; //端口號

// Socket 服務器端(簡單的發送信息)

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();

}

調用 accept 方法,阻塞等待客戶端連接;

利用 Socket 模擬了一個簡單的客戶端,只進行連接、讀取和打印;

在 Java 中,線程的實現是比較重量級的,所以線程的啟動或者銷毀是很消耗服務器的資源的,即使使用線程池來實現,使用上述傳統的 Socket 方式,當連接數極具上升也會帶來性能瓶頸,原因是線程的上線文切換開銷會在高并發的時候體現的很明顯,并且以上操作方式還是同步阻塞式的編程,性能問題在高并發的時候就會體現的尤為明顯。

以上的流程,如下圖:

AAffA0nNPuCLAAAAAElFTkSuQmCC

4.2 NIO 多路復用

介于以上高并發的問題,NIO 的多路復用功能就顯得意義非凡了。

NIO 是利用了單線程輪詢事件的機制,通過高效地定位就緒的 Channel,來決定做什么,僅僅 select 階段是阻塞的,可以有效避免大量客戶端連接時,頻繁線程切換帶來的問題,應用的擴展能力有了非常大的提高。

// NIO 多路復用

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() 創建一個 Selector,作為類似調度員的角色;

然后,創建一個 ServerSocketChannel,并且向 Selector 注冊,通過指定 SelectionKey.OP_ACCEPT,告訴調度員,它關注的是新的連接請求;

為什么我們要明確配置非阻塞模式呢?這是因為阻塞模式下,注冊操作是不允許的,會拋出 IllegalBlockingModeException 異常;

Selector 阻塞在 select 操作,當有 Channel 發生接入請求,就會被喚醒;

下面的圖,可以有效的說明 NIO 復用的流程:

AAffA0nNPuCLAAAAAElFTkSuQmCC

就這樣 NIO 的多路復用就大大提升了服務器端響應高并發的能力。

4.3 AIO 版 Socket 實現

Java 1.7 提供了 AIO 實現的 Socket 是這樣的,如下代碼:

// AIO線程復用版

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("服務端發送時間:" + 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);

五、總結

以上基本就是 IO 從 1.0 到目前版本(本文的版本)JDK 8 的核心使用操作了,可以看出來 IO 作為比較常用的基礎功能,發展變化的改動也很大,而且使用起來也越來越簡單了,IO 的操作也是比較好理解的,一個輸入一個輸出,掌握好了輸入輸出也就掌握好了 IO,Socket 作為網絡交互的集成功能,顯然 NIO 的多路復用,給 Socket 帶來了更多的活力和選擇,用戶可以根據自己的實際場景選擇相應的代碼策略。

六、參考文檔

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/533364.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/533364.shtml
英文地址,請注明出處:http://en.pswp.cn/news/533364.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java反射 獲取參數類型_Java反射帶參構造創建對象時如何自動轉換參數類型

需求是這樣的:有一個類,類的路徑知道,例如是com.xx.xx.xx其中有不同類型的成員變量(個數未知),有對應的setter和getter方法,有一個無參構造和一個全參構造。現在需要用反射機制...需求是這樣的:有一個類&am…

java的樂趣_分享java帶來的快樂

2013年9月17日#2011年7月18日#function reportError(sMessage, sUrl, sLine) {var str "";str " 錯誤信息:" sMessage "\n";str " 錯誤地址:" sUrl "\n";str " 錯誤行數:" sLine "\n";str &…

java arraylist底層實現原理_ArrayList和LinkedList底層原理

ArrayList和LinkedList都是List的實現類,是在日常開發中經常被使用到的兩個集合,我們來結合源碼看下兩個集合的不同之處。先來看下ArrayList的源碼:// 默認的初始化大小private static final int DEFAULT_CAPACITY 10;ArrayList的底層數數組…

java ee jaas_java-ee – Tomcat-Jaas – 如何檢索主題?

i knew that and it works, but I need to retrieve subject to get also roleprincipal不幸的是,它在Java EE中的工作方式不同. JAAS主題只是一個“主要包”,其中哪些代表用戶/調用者主體和/或角色主體根本不是標準化的.每個其他容器在這里做不同的事情. Javadoc for Tomcat’…

java jive歌詞_Java Jive_Manhattan Transfer with Phil Collins_高音質在線試聽_Java Jive歌詞|歌曲下載_酷狗音樂...

Manhattan Transfer with Phil Collins - Java Jive[id:$00000000][ar:曼哈頓行者爵士][ti:Java Jive (LP Version)][by:][hash:99bf26cac4ad13e15925a56eb724027f][al:][sign:][qq:][total:0][offset:0][00:00.05]The Manhattan Transfer - Java Jive[00:10.57]I …

java 3_Java 3 (Java的數據類型)

Java的數據類型主要內容:1Java數據類型的分類2.8種基本數據類型3.理解引用類型的特點一、什么是數據類型?計算機語言將數據按性質進行分類,每一類稱為一種數據類型;數據類型定義了數據的性質、取值范圍、存儲方式、對數據所能進行…

java快捷鍵 --_Java中的快捷方式“或分配”(| =)運算符

如果是關于可讀性,我就有了將測試數據與測試邏輯分離的概念。代碼示例:// declare dataDataType [] dataToTest new DataType[] {defaultStock,defaultWholesale,defaultRetail,defaultDelivery}// define logicboolean checkIfAnyNegative(DataType []…

tcp網絡通信教程 java_基于java TCP網絡通信的實例詳解

JAVA中設計網絡編程模式的主要有TCP和UDP兩種,TCP是屬于即時通信,UDP是通過數據包來進行通信,UDP當中就會牽扯到數據的解析和傳送。在安全性能方面,TCP要略勝一籌,通信過程中不容易出現數據丟失的現象,有一…

java博客論壇設計報告_javaweb課程設計報告個人博客網站的實現(Java).doc

javaweb課程設計報告個人博客網站的實現(Java)項目名稱: 個人博客網站的實現(Java) 學生姓名:學 號:班 級:指導教師:2014年12月23日目錄1 緒論11.1系統應用意義11.2主要設計任務11.3開發及運行環境11.3.1 JSP的基礎——…

java replace stringbuilder_java.lang.StringBuilder.replace()方法實例

全屏java.lang.StringBuilder.replace()方法按照這個順序,在指定的字符串的子字符串替換字符。子串開始在指定start的 索引,并延伸到該字符 end - 1,或如果序列的末端不存在這樣的字符。聲明以下是java.lang.StringBuilder.replace()方法的聲…

中小學課java_java畢業設計_springboot框架的中小學排課與實現

這是一個基于java的畢業設計項目,畢設課題為springboot框架的中小學排課與實現, 是一個采用b/s結構的javaweb項目, 開發工具eclipsei/eclipse, 項目框架jspspringbootmybatis, 中小學排課與實現采用mysql進行數據存儲, 并基于mybatis進行了orm實體關系映射, 該中小學排課與實現…

java 文件設置為只讀文件系統_Java如何設置文件為只讀?

在java編程中,如何設置文件為只讀?此示例演示如何使用File類的file.setReadOnly()和file.canWrite()方法設置文件為只讀模式。package com.yiibai;import java.io.File;public class ReadOnlyFile {public static void main(String[] args) {File file …

wordcount linux java_linux下在eclipse上運行hadoop自帶例子wordcount

啟動eclipse:打開windows->open perspective->other->map/reduce 可以看到map/reduce開發視圖。設置Hadoop location.打開windows->show view->other-> map/reduce Locations視圖,在點擊大象后【new Hadoop location】彈出的對話框(Ge…

php java執行linux_java_java執行Linux命令的方法,本文實例講述了java執行Linux命 - phpStudy...

java執行Linux命令的方法本文實例講述了java執行Linux命令的方法。分享給大家供大家參考。具體實現方法如下:public class StreamGobbler extends Thread {InputStream is;String type;public StreamGobbler(InputStream is, String type) {this.is is;this.type …

java怎么接收前端請求_前端json post 請求 后端怎么接收

前端提交POST /api/test HTTP/1.1Host: 192.168.135.69:81Connection: keep-aliveContent-Length: 18Origin: http://192.168.135.69:81User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15…

minimum在java中的意思_Java Calendar getMinimum()用法及代碼示例

Calendar類中的getMinimum(int calndr_field)方法用于返回此Calendar實例的給定日歷字段(int calndr_field)的最小值。用法:public abstract int getMinimum(int calndr_field)參數:該方法采用一個參數calndr_field,該參數表示要操作的日歷字段。返回值&…

django mysql 一對多_請教,django中 如何向帶有外鍵(一對多和多對多)數據庫中批量插入數據?...

已自行解決,代碼如下:json格式:[{"標題": "小武","內容": "測試","類型":["情感","文學","散文"]"文章資源":[{"title":"小武.1…

安裝php no permision,php安裝過程中的No package ‘xxx’ found問題

php No package ‘oniguruma’ found今天安裝php7.4的時候遇到這樣的一個報錯,然后yum install oniguruma oniguruma-devel,重試安裝php,依然報錯,又編譯安裝oniguruma,重試安裝php,還是報錯,問…

php httpclient.class.php,php實現httpclient類示例

class httpClient {public $buffer null; // buffer 獲取返回的字符串public $referer null; // referer 設置 HTTP_REFERER 的網址public $response null; // response 服務器響應的 header 信息public $request null; // request 發送到服務器的 header 信息private $…

大學php老師,php高校教師總結計劃系統

通過使用本系統,可以規范工作流程,提高辦公效率,增強團隊協同工作能力,實現科學的公文處理、事物管理、會議安排和人力管理,量化運營資源,預防管理真空,降低運行成本。還可以實現便利的信息發布…