【Java】NIO

1. Java NIO 簡介

Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,能夠替代標準的Java IO API。
NIO與原來的IO有一樣的做用和目的,可是使用的方式徹底不一樣,NIO支持面向緩衝區的、基於通道的IO操做。NIO將以更加高效的方式進行文件的讀寫操做。java

2. Java NIO 與 IO 的主要區別

IO NIO
面向流(Stream Oriented) 面向緩衝區(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)
(無) 選擇器(Selectors)

3. 緩衝區(Buffer)和通道(Channel)

Java NIO系統的核心在於:通道(Channel)和緩衝區(Buffer)。通道表示打開到 IO 設備(例如:文件、套接字)的鏈接。若須要使用 NIO 系統,須要獲取用於鏈接 IO 設備的通道以及用於容納數據的緩衝區。而後操做緩衝區,對數據進行處理。數組

簡而言之,Channel 負責傳輸, Buffer 負責存儲服務器

緩衝區

緩衝區(Buffer):一個用於特定基本數據類型的容器。由 java.nio 包定義的,全部緩衝區都是 Buffer 抽象類的子類。網絡

Java NIO 中的 Buffer 主要用於與 NIO 通道進行交互,數據是從通道讀入緩衝區,從緩衝區寫入通道中的。app

Buffer 就像一個數組,能夠保存多個相同類型的數據。根據數據類型不一樣(boolean 除外) ,有如下 Buffer 經常使用子類:dom

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

上述 Buffer 類 他們都採用類似的方法進行管理數據,只是各自管理的數據類型不一樣而已。都是經過以下方法獲取一個 Buffer 對象:
static XxxBuffer allocate(int capacity) : 建立一個容量爲 capacity 的 XxxBuffer 對象socket

緩衝區的基本屬性

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() 方法來肯定。提供此方法是爲了可以在性能關鍵型代碼中執行顯式緩衝區管理。


4. 文件通道(FileChannel)

通道(Channel):由 java.nio.channels 包定義的。Channel 表示 IO 源與目標打開的鏈接。
Channel 相似於傳統的「流」。只不過 Channel 自己不能直接訪問數據,Channel 只能與 Buffer 進行交互。



Java 爲 Channel 接口提供的最主要實現類以下:

  • FileChannel:用於讀取、寫入、映射和操做文件的通道。
  • DatagramChannel:經過 UDP 讀寫網絡中的數據通道。
  • SocketChannel:經過 TCP 讀寫網絡中的數據。
  • ServerSocketChannel:能夠監聽新進來的 TCP 鏈接,對每個新進來的鏈接都會建立一個 SocketChannel。

獲取通道

獲取通道的一種方式是對支持通道的對象調用 getChannel() 方法。支持通道的類以下:

  • FileInputStream
  • FileOutputStream
  • RandomAccessFile
  • DatagramSocket
  • Socket
  • ServerSocket

獲取通道的其餘方式是使用 Files 類的靜態方法 newByteChannel() 獲取字節通道。或者經過通道的靜態方法 open() 打開並返回指定通道。

通道的數據傳輸

將 Buffer 中數據寫入 Channel,例如:

//將 Buffer 中數據寫入 Channel 中
int bytesWritten = inChannel.write(buf);

從 Channel 讀取數據到 Buffer,例如:

//從 Channel 讀取數據到 Buffer 中
int bytesRead = inChannel.read(buf);

分散(Scatter)和彙集(Gather)

分散讀取(Scattering Reads)是指從 Channel 中讀取的數據「分散」到多個 Buffer 中。

注意:按照緩衝區的順序,從 Channel 中讀取的數據依次將 Buffer 填滿。

彙集寫入(Gathering Writes)是指將多個 Buffer 中的數據「彙集」到 Channel。

注意:按照緩衝區的順序,寫入 position 和 limit 之間的數據到 Channel 。

transferFrom()

將數據從源通道傳輸到其餘 Channel 中:

RandomAccessFile fromFile = new RandomAccessFile("data/fromFile.txt", "rw");
//獲取FileChannel
FileChannel fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("data/toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();

//定義傳輸位置
long position = 0L;

//最多傳輸的的字節數
long count = fromChannel. size();

//將數據從源通道傳輸到另外一個通道
toChannel.transferFrom(fromChannel, count, position);

transferTo()

將數據從源通道傳輸到其餘 Channel 中:

RandomAccessFile fromFile = new RandomAccessFile("data/ fromFile.txt", "rw");
//獲取FileChannel
FileChannel fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("data/toFile.txt","rw");
FileChannel toChannel = toFile. getChannel();

//定義傳輸位置
long position = 0L;

//最多傳輸的的字節數
long count = fromChannel.size();

//將數據從源通道傳輸到另外一個通道
fromChannel . transferTo(position, count, toChannel);

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:經過調用 Selector.open() 方法建立一個 Selector。

//建立選擇器
Selector selector = Selector.open();

向選擇器註冊通道:SelectableChannel.register(Selector sel, int ops)

//建立一個Socket 套接字
Socket socket = new Socket(InetAddress . getByName("127.0.0.1"), 9898);

//獲取SocketChannel
SocketChannel channel = socket.getChannel();

//建立選擇器。
Selector selector = Selector.open();

//將SocketChannel 切換到非阻塞模式。
channel.configureBlocking(false);

//向Selector 註冊Channel
SelectionKey key = channel.register(selector, SelectionKey.OP_READ;

選擇器(Selector)的應用

當調用 register(Selector sel, int ops) 將通道註冊選擇器時,選擇器對通道的監聽事件,須要經過第二個參數 ops 指定。

能夠監聽的事件類型(可以使用 SelectionKey 的四個常量表示):

  • 讀: SelectionKey.OP_READ (1)
  • 寫: SelectionKey.OP_WRITE (4)
  • 鏈接: SelectionKey.OP_CONNECT (8)
  • 接收: SelectionKey.OP_ACCEPT (16)

若註冊時不止監聽一個事件,則可使用「位或」操做符鏈接。例:

//註冊「監聽事件」
int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE;

SelectionKey

SelectionKey:表示 SelectableChannel 和 Selector 之間的註冊關係。每次向選擇器註冊通道時就會選擇一個事件(選擇鍵)。選擇鍵包含兩個表示爲整數值的操做集。操做集的每一位都表示該鍵的通道所支持的一類可選擇操做。

方法 描述
int interestOps() 獲取感興趣事件集合
int readyOps() 獲取通道已經準備就緒的操做的集合
SelectableChannel channel() 獲取註冊通道
Selector selector() 返回選擇器
boolean isReadable() 檢測 Channal 中讀事件是否就緒
boolean isWritable() 檢測 Channal 中寫事件是否就緒
boolean isConnectable() 檢測 Channel 中鏈接是否就緒
boolean isAcceptable() 檢測 Channel 中接收是否就緒

Selector 的經常使用方法

方法 描述
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網絡套接字的通道。

操做步驟:

  1. 打開 SocketChannel
  2. 讀寫數據
  3. 關閉 SocketChannel

ServerSocketChannel

Java NIO中的 ServerSocketChannel 是一個能夠監聽新進來的TCP鏈接的通道,就像標準IO中的ServerSocket同樣。

DatagramChannel

Java NIO中的DatagramChannel是一個能收發UDP包的通道。

操做步驟:

  1. 打開 DatagramChannel
  2. 接收/發送數據

6. 管道(Pipe)

Java NIO 管道是2個線程之間的單向數據鏈接。Pipe有一個source通道和一個sink通道。數據會被寫到sink通道,從source通道讀取。

向管道寫數據

@Test
public void test1() throws IOException{
    String str =「測試數據";

    //建立管道
    Pipe pipe = Pipe。open();

    //向管道寫輸入
    Pipe.SinkChannel sinkChannel = pipe.sink();

    //經過SinkChannel 的write()方法寫數據
    ByteBuffer buf = ByteBuffer.allocate(1024);
    buf.clear();
    buf.put(str.getBytes());
    buf.flip();

    while(buf.hasRemaining()){
        sinkChannel.write(buf);
    }
}

從管道讀取數據

從讀取管道的數據,須要訪問source通道。

//從管道讀取數據
Pipe.SourceChannel sourceChannel = pipe.source();

調用source通道的read()方法來讀取數據

//調用 SourceChannel 的 read() 方法讀取數據
ByteBuffer buf = ByteBuffer.allocate(1024);
sourceChannel.read(buf);

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 語句的擴展形式:

try(須要關閉的資源聲明) {
    //可能發生異常的語句
}catch(異常類型 變量名) {
    //異常的處理語句
}
……
finally {
    //必定執行的語句
}

當 try 代碼塊結束時,自動釋放資源。所以不須要顯示的調用 close() 方法。該形式也稱爲「帶資源的 try 語句」。

注意:

  • try 語句中聲明的資源被隱式聲明爲 final ,資源的做用侷限於帶資源的 try 語句
  • 能夠在一條 try 語句中管理多個資源,每一個資源以「;」 隔開便可。
  • 須要關閉的資源,必須實現了 AutoCloseable 接口或其自接口 Closeable
相關文章
相關標籤/搜索