文章目錄
-
- 1. Java NIO 簡介
- 2. Java NIO 與 IO 的主要差別
- 3. 緩沖區(Buffer)和通道(Channel)
-
- 緩沖區(Buffer)
- 直接與非直接緩沖區
- 通道(Channel)
- 4. 檔案通道(FileChannel)
-
- 擷取通道
- 通道的資料傳輸
- 分散(Scatter)和聚集(Gather)
- 5. NIO 的非阻塞式網絡通信
-
- 阻塞與非阻塞
- 選擇器(Selector)
- SelectionKey
- SocketChannel、ServerSocketChannel、DatagramChannel
- 6. 管道(Pipe)
-
- 向管道寫資料
- 從管道讀取資料
- 7. Java NIO2 (Path、Paths 與 Files )
-
- NIO.2
- Path 與 Paths
- Files 類
- 自動資源管理
1. Java NIO 簡介
Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,可以替代标準的Java IO API。NIO與原來的IO有同樣的作用和目的,但是使用的方式完全不同,NIO支援面向緩沖區的、基于通道的IO操作。NIO将以更加高效的方式進行檔案的讀寫操作。
2. Java NIO 與 IO 的主要差別

3. 緩沖區(Buffer)和通道(Channel)
Java NIO系統的核心在于:通道(Channel)和緩沖區(Buffer)。通道表示打開到 IO 裝置(例如:檔案、套接字)的連接配接。若需要使用 NIO 系統,需要擷取用于連接配接 IO 裝置的通道以及用于容納資料的緩沖區。然後操作緩沖區,對資料進行處理。
簡而言之,Channel 負責傳輸, Buffer 負責存儲
緩沖區(Buffer)
●緩沖區(Buffer):一個用于特定基本資料類型的容器。由 java.nio 包定義的,所有緩沖區都是 Buffer 抽象類的子類。
●Java NIO 中的 Buffer 主要用于與 NIO 通道進行互動,資料是從通道讀入緩沖區,從緩沖區寫入通道中的。
●Buffer 就像一個數組,可以儲存多個相同類型的資料。根據資料類型不同(boolean 除外) ,有以下 Buffer 常用子類:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
上述 Buffer 類 他們都采用相似的方法進行管理資料,隻是各自管理的資料類型不同而已。都是通過如下方法擷取一個 Buffer 對象:
static XxxBuffer allocate(int capacity) : 建立一個容量為 capacity 的 XxxBuffer 對象
基本屬性:
●Buffer 中的重要概念:
● 容量 (capacity) :表示 Buffer 最大資料容量,緩沖區容量不能為負,并且建立後不能更改。
● 限制 (limit):第一個不應該讀取或寫入的資料的索引,即位于 limit 後的資料不可讀寫。緩沖區的限制不能為負,并且不能大于其容量。
● 位置 (position):下一個要讀取或寫入的資料的索引。緩沖區的位置不能為負,并且不能大于其限制
● 标記 (mark)與重置 (reset):标記是一個索引,通過 Buffer 中的 mark() 方法指定 Buffer 中一個特定的 position,之後可以通過調用 reset() 方法恢複到這個 position.
● 标記、位置、限制、容量遵守以下不變式: 0 <= mark <= position <= limit <= capacity
Buffer 的常用方法
方 法 | 描 述 |
---|---|
Buffer clear() | 清空緩沖區并傳回對緩沖區的引用 |
Buffer flip() | 将緩沖區的界限設定為目前位置,并将目前位置充值為 0 |
int capacity() | 傳回 Buffer 的 capacity 大小 |
boolean hasRemaining() | 判斷緩沖區中是否還有元素 |
int limit() | 傳回 Buffer 的界限(limit) 的位置 |
Buffer limit(int n) | 将設定緩沖區界限為 n, 并傳回一個具有新 limit 的緩沖區對象 |
Buffer mark() | 對緩沖區設定标記 |
int position() | 傳回緩沖區的目前位置 position |
Buffer position(int n) | 将設定緩沖區的目前位置為 n , 并傳回修改後的 Buffer 對象 |
int remaining() | 傳回 position 和 limit 之間的元素個數 |
Buffer reset() | 将位置 position 轉到以前設定的 mark 所在的位置 |
Buffer rewind() | 将位置設為為 0, 取消設定的 mark |
緩沖區的資料操作
●Buffer 所有子類提供了兩個用于資料操作的方法:get() 與 put() 方法
✔ 擷取 Buffer 中的資料
get() :讀取單個位元組
get(byte[] dst):批量讀取多個位元組到 dst 中
get(int index):讀取指定索引位置的位元組(不會移動 position)
✔ 放入資料到 Buffer 中
put(byte b):将給定單個位元組寫入緩沖區的目前位置
put(byte[] src):将 src 中的位元組寫入緩沖區的目前位置
put(int index, byte b):将指定位元組寫入緩沖區的索引位置(不會移動 position)
直接與非直接緩沖區
●位元組緩沖區要麼是直接的,要麼是非直接的。如果為直接位元組緩沖區,則 Java 虛拟機會盡最大努力直接在此緩沖區上執行本機 I/O 操作。也就是說,在每次調用基礎作業系統的一個本機 I/O 操作之前(或之後),虛拟機都會盡量避免将緩沖區的内容複制到中間緩沖區中(或從中間緩沖區中複制内容)。
●直接位元組緩沖區可以通過調用此類的 allocateDirect() 工廠方法來建立。此方法傳回的緩沖區進行配置設定和取消配置設定所需成本通常高于非直接緩沖區。直接緩沖區的内容可以駐留在正常的垃圾回收堆之外,是以,它們對應用程式的記憶體需求量造成的影響可能并不明顯。是以,建議将直接緩沖區主要配置設定給那些易受基礎系統的本機 I/O 操作影響的大型、持久的緩沖區。一般情況下,最好僅在直接緩沖區能在程式性能方面帶來明顯好處時配置設定它們。
●直接位元組緩沖區還可以通過 FileChannel 的 map() 方法 将檔案區域直接映射到記憶體中來建立。該方法傳回MappedByteBuffer 。Java 平台的實作有助于通過 JNI 從本機代碼建立直接位元組緩沖區。如果以上這些緩沖區中的某個緩沖區執行個體指的是不可通路的記憶體區域,則試圖通路該區域不會更改該緩沖區的内容,并且将會在通路期間或稍後的某個時間導緻抛出不确定的異常。
●位元組緩沖區是直接緩沖區還是非直接緩沖區可通過調用其 isDirect() 方法來确定。提供此方法是為了能夠在性能關鍵型代碼中執行顯式緩沖區管理。
非直接緩沖區
直接緩沖區
通道(Channel)
●通道(Channel):由 java.nio.channels 包定義的。Channel 表示 IO 源與目标打開的連接配接。Channel 類似于傳統的“流”。隻不過 Channel 本身不能直接通路資料,Channel 隻能與Buffer 進行互動。
●Java 為 Channel 接口提供的最主要實作類如下:
- FileChannel:用于讀取、寫入、映射和操作檔案的通道。
- DatagramChannel:通過 UDP 讀寫網絡中的資料通道。
- SocketChannel:通過 TCP 讀寫網絡中的資料。
- ServerSocketChannel:可以監聽新進來的 TCP連接配接,對每一個新進來 的連接配接都會建立一個 SocketChannel。
4. 檔案通道(FileChannel)
擷取通道
●擷取通道的一種方式是對支援通道的對象調用getChannel() 方法。支援通道的類如下:
- FileInputStream
- FileOutputStream
- RandomAccessFile
- DatagramSocket
- Socket
- ServerSocket
擷取通道的其他方式是使用 Files 類的靜态方法 newByteChannel() 擷取位元組通道。或者通過通道的靜态方法 open() 打開并傳回指定通道。
通道的資料傳輸
将 Buffer 中資料寫入 Channel
例如:
從 Channel 讀取資料到 Buffer
例如:
分散(Scatter)和聚集(Gather)
● 分散讀取(Scattering Reads)是指從 Channel 中讀取的資料“分散”到多個 Buffer 中。
注意:按照緩沖區的順序,從 Channel 中讀取的資料依次将 Buffer 填滿。
● 聚集寫入(Gathering Writes)是指将多個 Buffer 中的資料“聚集”到 Channel。
注意:按照緩沖區的順序,寫入 position 和 limit 之間的資料到 Channel 。
transferFrom()
将資料從源通道傳輸到其他 Channel 中:
transferTo()
将資料從源通道傳輸到其他 Channel 中:
FileChannel 的常用方法
方 法 | 描 述 |
---|---|
int read(ByteBuffer dst) | 從 Channel 中讀取資料到 ByteBuffer |
long read(ByteBuffer[] dsts) | 将 Channel 中的資料“分散”到 ByteBuffer[] |
int write(ByteBuffer src) | 将 ByteBuffer 中的資料寫入到 Channel |
long write(ByteBuffer[] srcs) | 将 ByteBuffer[] 中的資料“聚集”到 Channel |
long position() | 傳回此通道的檔案位置 |
FileChannel position(long p) | 設定此通道的檔案位置 |
long size() | 傳回此通道的檔案的目前大小 |
FileChannel truncate(long s) | 将此通道的檔案截取為給定大小 |
void force(boolean metaData) | 強制将所有對此通道的檔案更新寫入到儲存設備中 |
5. NIO 的非阻塞式網絡通信
阻塞與非阻塞
傳統的 IO 流都是阻塞式的。也就是說,當一個線程調用 read() 或 write() 時,該線程被阻塞,直到有一些資料被讀取或寫入,該線程在此期間不能執行其他任務。是以,在完成網絡通信進行 IO 操作時,由于線程會阻塞,是以伺服器端必須為每個用戶端都提供一個獨立的線程進行處理,當伺服器端需要處理大量用戶端時,性能急劇下降。
Java NIO 是非阻塞模式的。當線程從某通道進行讀寫資料時,若沒有資料可用時,該線程可以進行其他任務。線程通常将非阻塞 IO 的空閑時間用于在其他通道上執行 IO 操作,是以單獨的線程可以管理多個輸入和輸出通道。是以,NIO 可以讓伺服器端使用一個或有限幾個線程來同時處理連接配接到伺服器端的所有用戶端。
選擇器(Selector)
選擇器(Selector) 是 SelectableChannle 對象的多路複用器,Selector 可以同時監控多個 SelectableChannel 的 IO 狀況,也就是說,利用 Selector 可使一個單獨的線程管理多個 Channel。Selector 是非阻塞 IO 的核心。
SelectableChannle 的結構如下圖:
●建立 Selector :通過調用 Selector.open() 方法建立一個 Selector。
● 向選擇器注冊通道:SelectableChannel.register(Selector sel, int ops)
SelectionKey
●當調用 register(Selector sel, int ops) 将通道注冊選擇器時,選擇器對通道的監聽事件,需要通過第二個參數 ops 指定。
● 可以監聽的事件類型(可使用 SelectionKey 的四個常量表示):
- 讀 : SelectionKey.OP_READ (1)
- 寫 : SelectionKey.OP_WRITE (4)
- 連接配接 : SelectionKey.OP_CONNECT (8)
- 接收 : SelectionKey.OP_ACCEPT (16)
●若注冊時不止監聽一個事件,則可以使用“位或”操作符連接配接。
例:
SelectionKey:表示 SelectableChannel 和 Selector 之間的注冊關系。每次向選擇器注冊通道時就會選擇一個事件(選擇鍵)。選擇鍵包含兩個表示為整數值的操作集。操作集的每一位都表示該鍵的通道所支援的一類可選擇操作。
方 法 | 描 述 |
---|---|
int interestOps() | 擷取感興趣事件集合 |
int readyOps() | 擷取通道已經準備就緒的操作的集合 |
SelectableChannel channel() | 擷取注冊通道 |
Selector selector() | 傳回選擇器 |
boolean isReadable() | 檢測 Channal 中讀事件是否就緒 |
boolean isWritable() | 檢測 Channal 中寫事件是否就緒 |
boolean isConnectable() | 檢測 Channel 中連接配接是否就緒 |
boolean isAcceptable() | 檢測 Channel 中接收是否就緒 |
Set keys() | 所有的 SelectionKey 集合。代表注冊在該Selector上的Channel |
selectedKeys() | 被選擇的 SelectionKey 集合。傳回此Selector的已選擇鍵集 |
int select() | 監控所有注冊的Channel,當它們中間有需要處理的 IO 操作時,該方法傳回,并将對應得的 SelectionKey 加入被選擇的SelectionKey 集合中,該方法傳回這些 Channel 的數量。 |
int select(long timeout) | 可以設定逾時時長的 select() 操作 |
int selectNow() | 執行一個立即傳回的 select() 操作,該方法不會阻塞線程 |
Selector wakeup() | 使一個還未傳回的 select() 方法立即傳回 |
void close() | 關閉該選擇器 |
SocketChannel、ServerSocketChannel、DatagramChannel
SocketChannel
Java NIO中的SocketChannel是一個連接配接到TCP網絡套接字的通道。
操作步驟:
- 打開 SocketChannel
- 讀寫資料
- 關閉 SocketChannel
ServerSocketChannel
Java NIO中的 ServerSocketChannel 是一個可以監聽新進來的TCP連接配接的通道,就像标準IO中 的ServerSocket一樣。
DatagramChannel
Java NIO中的DatagramChannel是一個能收發UDP包的通道。
操作步驟:
- 打開 DatagramChannel
- 接收/發送資料
6. 管道(Pipe)
Java NIO 管道是2個線程之間的單向資料連接配接。Pipe有一個source通道和一個sink通道。資料會被寫到sink通道,從source通道讀取。
向管道寫資料
從管道讀取資料
7. Java NIO2 (Path、Paths 與 Files )
NIO.2
随着 JDK 7 的釋出,Java對NIO進行了極大的擴充,增強了對檔案處理和檔案系統特性的支援,以至于我們稱他們為 NIO.2。因為 NIO 提供的一些功能,NIO已經成為檔案進行中越來越重要的部分。
Path 與 Paths
java.nio.file.Path 接口代表一個平台無關的平台路徑,描述了目錄結構中檔案的位置。
Paths 提供的 get() 方法用來擷取 Path 對象:
- Path get(String first, String … more) : 用于将多個字元串串連成路徑。
Path 常用方法:
- boolean endsWith(String path) : 判斷是否以 path 路徑結束
- boolean startsWith(String path) : 判斷是否以 path 路徑開始
- boolean isAbsolute() : 判斷是否是絕對路徑
- Path getFileName() : 傳回與調用 Path 對象關聯的檔案名
- Path getName(int idx) : 傳回的指定索引位置 idx 的路徑名稱
- int getNameCount() : 傳回Path 根目錄後面元素的數量
- Path getParent() :傳回Path對象包含整個路徑,不包含 Path 對象指定的檔案路徑
- Path getRoot() :傳回調用 Path 對象的根路徑
- Path resolve(Path p) :将相對路徑解析為絕對路徑
- Path toAbsolutePath() : 作為絕對路徑傳回調用 Path 對象
- String toString() : 傳回調用 Path 對象的字元串表示形式
Files 類
java.nio.file.Files 用于操作檔案或目錄的工具類。
Files常用方法:
- Path copy(Path src, Path dest, CopyOption … how) : 檔案的複制
- Path createDirectory(Path path, FileAttribute<?> … attr) : 建立一個目錄
- Path createFile(Path path, FileAttribute<?> … arr) : 建立一個檔案
- void delete(Path path) : 删除一個檔案
- Path move(Path src, Path dest, CopyOption…how) : 将 src 移動到 dest 位置
- long size(Path path) : 傳回 path 指定檔案的大小
Files常用方法:用于判斷
- boolean exists(Path path, LinkOption … opts) : 判斷檔案是否存在
- boolean isDirectory(Path path, LinkOption … opts) : 判斷是否是目錄
- boolean isExecutable(Path path) : 判斷是否是可執行檔案
- boolean isHidden(Path path) : 判斷是否是隐藏檔案
- boolean isReadable(Path path) : 判斷檔案是否可讀
- boolean isWritable(Path path) : 判斷檔案是否可寫
- boolean notExists(Path path, LinkOption … opts) : 判斷檔案是否不存在
- public static <A extends BasicFileAttributes> A readAttributes(Path path,Class<A> type,LinkOption… options) : 擷取與 path 指定的檔案相關聯的屬性。
Files常用方法:用于操作内容
- SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 擷取與指定檔案的連接配接,how 指定打開方式。
- DirectoryStream newDirectoryStream(Path path) : 打開 path 指定的目錄
- InputStream newInputStream(Path path, OpenOption…how):擷取 InputStream 對象
- OutputStream newOutputStream(Path path, OpenOption…how) : 擷取 OutputStream 對象
自動資源管理
Java 7 增加了一個新特性,該特性提供了另外一種管理資源的方式,這種方式能自動關閉檔案。這個特性有時被稱為自動資源管理(Automatic Resource Management, ARM), 該特性以 try 語句的擴充版為基礎。自動資源管理主要用于,當不再需要檔案(或其他資源)時,可以防止無意中忘記釋放它們。
自動資源管理基于 try 語句的擴充形式: