引言
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中的數據寫入通道。
當然,也有區別,主要體現在如下兩點:
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個變量的情況如下圖
在對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 頭條 必過) ~.~