JAVA開發NIO核心設計理念與IO的區別解析

編程語言 Java 程序員 C語言 圖靈學院 圖靈學院 2017-09-23

引言

Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,可以替代標準的Java IO API。本系列教程將有助於你學習和理解Java NIO。

一丶背景知識

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

首先,這幾個概念非常容易搞混淆,但NIO中又有涉及,所以總結一下[1]。

1丶同步:API調用返回時調用者就知道操作的結果如何了(實際讀取/寫入了多少字節)。

2丶異步:相對於同步,API調用返回時調用者不知道操作的結果,後面才會回調通知結果。

3丶阻塞:當無數據可讀,或者不能寫入所有數據時,掛起當前線程等待。

4丶非阻塞:讀取時,可以讀多少數據就讀多少然後返回,寫入時,可以寫入多少數據就寫入多少然後返回。

對於I/O操作,根據Oracle官網的文檔,同步異步的劃分標準是“調用者是否需要等待I/O操作完成”,這個“等待I/O操作完成”的意思不是指一定要讀取到數據或者說寫入所有數據,而是指真正進行I/O操作時,比如數據在TCP/IP協議棧緩衝區和JVM緩衝區之間傳輸的這段時間,調用者是否要等待。

所以,我們常用的 read() 和 write() 方法都是同步I/O,同步I/O又分為阻塞和非阻塞兩種模式,如果是非阻塞模式,檢測到無數據可讀時,直接就返回了,並沒有真正執行I/O操作。

總結就是,Java中實際上只有 同步阻塞I/O、同步非阻塞I/O 與 異步I/O 三種機制,我們下文所說的是前兩種,JDK 1.7才開始引入異步 I/O,那稱之為NIO.2。

二丶傳統IO

我們知道,一個新技術的出現總是伴隨著改進和提升,Java NIO的出現亦如此。

傳統 I/O 是阻塞式I/O,主要問題是系統資源的浪費。比如我們為了讀取一個TCP連接的數據,調用 InputStream 的 read() 方法,這會使當前線程被掛起,直到有數據到達才被喚醒,那該線程在數據到達這段時間內,佔用著內存資源(存儲線程棧)卻無所作為,也就是俗話說的佔著茅坑不拉屎,為了讀取其他連接的數據,我們不得不啟動另外的線程。在併發連接數量不多的時候,這可能沒什麼問題,然而當連接數量達到一定規模,內存資源會被大量線程消耗殆盡。另一方面,線程切換需要更改處理器的狀態,比如程序計數器、寄存器的值,因此非常頻繁的在大量線程之間切換,同樣是一種資源浪費。

隨著技術的發展,現代操作系統提供了新的I/O機制,可以避免這種資源浪費。基於此,誕生了Java NIO,NIO的代表性特徵就是非阻塞I/O。緊接著我們發現,簡單的使用非阻塞I/O並不能解決問題,因為在非阻塞模式下,read()方法在沒有讀取到數據時就會立即返回,不知道數據何時到達的我們,只能不停的調用read()方法進行重試,這顯然太浪費CPU資源了,從下文可以知道,Selector組件正是為解決此問題而生。

三丶Java NIO 核心組件

1.Channel

概念

Java NIO中的所有I/O操作都基於Channel對象,就像流操作都要基於Stream對象一樣,因此很有必要先了解Channel是什麼。以下內容摘自JDK 1.8的文檔

A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing.

從上述內容可知,一個Channel(通道)代表和某一實體的連接,這個實體可以是文件、網絡套接字等。也就是說,通道是Java NIO提供的一座橋樑,用於我們的程序和操作系統底層I/O服務進行交互。

通道是一種很基本很抽象的描述,和不同的I/O服務交互,執行不同的I/O操作,實現不一樣,因此具體的有FileChannel、SocketChannel等。

通道使用起來跟Stream比較像,可以讀取數據到Buffer中,也可以把Buffer中的數據寫入通道。

JAVA開發NIO核心設計理念與IO的區別解析

當然,也有區別,主要體現在如下兩點:

1丶一個通道,既可以讀又可以寫,而一個Stream是單向的(所以分 InputStream 和 OutputStream)

2丶通道有非阻塞I/O模式

實現

Java NIO中最常用的通道實現是如下幾個,可以看出跟傳統的 I/O 操作類是一一對應的。

1丶FileChannel:讀寫文件

2丶DatagramChannel: UDP協議網絡通信

3丶SocketChannel:TCP協議網絡通信

4丶ServerSocketChannel:監聽TCP連接

2.Buffer

NIO中所使用的緩衝區不是一個簡單的byte數組,而是封裝過的Buffer類,通過它提供的API,我們可以靈活的操縱數據,下面細細道來。

與Java基本類型相對應,NIO提供了多種 Buffer 類型,如ByteBuffer、CharBuffer、IntBuffer等,區別就是讀寫緩衝區時的單位長度不一樣(以對應類型的變量為單位進行讀寫)。

Buffer中有3個很重要的變量,它們是理解Buffer工作機制的關鍵,分別是

1丶capacity (總容量)

2丶position (指針當前位置)

3丶limit (讀/寫邊界位置)

Buffer的工作方式跟C語言裡的字符數組非常的像,類比一下,capacity就是數組的總長度,position就是我們讀/寫字符的下標變量,limit就是結束符的位置。Buffer初始時3個變量的情況如下圖

JAVA開發NIO核心設計理念與IO的區別解析

在對Buffer進行讀/寫的過程中,position會往後移動,而 limit 就是 position 移動的邊界。由此不難想象,在對Buffer進行寫入操作時,limit應當設置為capacity的大小,而對Buffer進行讀取操作時,limit應當設置為數據的實際結束位置。(注意:將Buffer數據 寫入 通道是Buffer 讀取 操作,從通道 讀取 數據到Buffer是Buffer 寫入 操作)

在對Buffer進行讀/寫操作前,我們可以調用Buffer類提供的一些輔助方法來正確設置 position 和 limit 的值,主要有如下幾個

1丶flip(): 設置 limit 為 position 的值,然後 position 置為0。對Buffer進行讀取操作前調用。

2丶rewind(): 僅僅將 position 置0。一般是在重新讀取Buffer數據前調用,比如要讀取同一個Buffer的數據寫入多個通道時會用到。

3丶clear(): 回到初始狀態,即 limit 等於 capacity,position 置0。重新對Buffer進行寫入操作前調用。

4丶compact(): 將未讀取完的數據(position 與 limit 之間的數據)移動到緩衝區開頭,並將 position 設置為這段數據末尾的下一個位置。其實就等價於重新向緩衝區中寫入了這麼一段數據。

四丶核心概念:通道和緩衝區

1) 概述:

通道和緩衝區是NIO中的核心對象,幾乎在每一個I/O操作中都要使用它們。

通道Channel是對原I/O包中的流的模擬。到任何目的地(或來自任何地方)的所有數據都必須通過一個Channel對象。

緩衝區Buffer實質上是一個容器對象。發送給一個通道的所有對象都必須首先放到緩衝區中;同樣地,從通道中讀取的任何數據都要讀到緩衝區中。

2) 緩衝區:

Buffer是一個容器對象,它包含一些要寫入或者剛讀出的數據。在NIO中加入Buffer對象,體現了新庫與原I/O的一個重要區別。在面向流的I/O中,您將數據直接寫入或者將數據直接讀到Stream對象中。

在NIO庫中,所有數據都是用緩衝區處理的。在讀取數據時,它是直接讀到緩衝區中的。在寫入數據時,它是寫入到緩衝區中的。任何時候訪問NIO中的數據,您都是將它放到緩衝區中。

緩衝區實質上是一個數組。通常它是一個字節數組,但是也可以使用其他種類的數組。但是一個緩衝區不僅僅是一個數組。緩衝區提供了對數據的結構化訪問,而且還可以跟蹤系統的讀/寫進程。

最常用的緩衝區類型是ByteBuffer。 一個ByteBuffer可以在其底層字節數組上進行get/set操作(即字節的獲取和設置)。

ByteBuffer不是NIO中唯一的緩衝區類型。事實上,對於每一種基本Java類型都有一種緩衝區類型:

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

LongBuffer

FloatBuffer

DoubleBuffer

每一個Buffer類都是Buffer接口的一個實例。 除了ByteBuffer, 每一個Buffer類都有完全一樣的操作,只是它們所處理的數據類型不一樣。因為大多數標準I/O操作都使用ByteBuffer,所以它具有所有共享的緩衝區操作以及一些特有的操作。

五丶Java NIO和IO的主要區別

下表總結了Java NIO和IO之間的主要差別,我會更詳細地描述表中每部分的差異。

IO NIO

Stream oriented Buffer oriented

Blocking IO Non blocking IO

Selectors

面向流與面向緩衝

Java NIO和IO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。 Java IO面向流意味著每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前後移動流中的數據。如果需要前後移動從流中讀取的數據,需要先將它緩存到一個緩衝區。 Java NIO的緩衝導向方法略有不同。數據讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的數據。而且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裡尚未處理的數據。

阻塞與非阻塞IO

Java IO的各種流是阻塞的。這意味著,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再幹任何事情了。 Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什麼都不會獲取。而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閒時間用於在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。

選擇器(Selectors)

Java NIO的選擇器允許一個單獨的線程來監視多個輸入通道,你可以註冊多個通道使用一個選擇器,然後使用一個單獨的線程來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。

NIO和IO如何影響應用程序的設計

無論您選擇IO或NIO工具箱,可能會影響您應用程序設計的以下幾個方面:

對NIO或IO類的API調用。

數據處理。

用來處理數據的線程數。

API調用

當然,使用NIO的API調用時看起來與使用IO時有所不同,但這並不意外,因為並不是僅從一個InputStream逐字節讀取,而是數據必須先讀入緩衝區再處理。

數據處理

使用純粹的NIO設計相較IO設計,數據處理也受到影響。

總結

以 上就是對

JAVA開發NIO核心設計理念與IO的區別解析問題及其優化總結,分享給大家,希望大家可以瞭解什麼是JAVA開發NIO核心設計理念與IO的區別解析問題及其優化。覺得收穫的話可以點個關注收藏轉發一波喔,謝謝大佬們支持!

1、多寫多敲代碼,好的代碼與紮實的基礎知識一定是實踐出來的

2、可以去百度搜索騰訊課堂圖靈學院的視頻來學習一下java架構實戰案例,還挺不錯的。

最後,每一位讀到這裡的網友,感謝你們能耐心地看完。希望在成為一名更優秀的Java程序員的道路上,我們可以一起學習、一起進步。

3丶想了解學習以上課程內容可加群:469717771 驗證碼(06 頭條 必過) ~.~

JAVA開發NIO核心設計理念與IO的區別解析

相關推薦

推薦中...