Java NIO 快速指南



Java NIO - 概述

Java.nio 包是在 Java 1.4 中引入的。與 Java I/O 相比,Java NIO 引入了面向緩衝區和通道的資料流進行 I/O 操作,從而提高了執行速度和效能。

NIO API 還提供了選擇器,該選擇器引入了非同步或非阻塞方式監聽多個通道 I/O 事件的功能。在 NIO 中,包括將緩衝區填充和清空到作業系統等最耗時的 I/O 活動都得到了加速。

NIO API 的核心抽象如下:

  • 緩衝區,用於儲存資料;字元集及其關聯的解碼器和編碼器,用於在位元組和 Unicode 字元之間轉換。

  • 各種型別的通道,表示與能夠執行 I/O 操作的實體的連線。

  • 選擇器和選擇鍵,它們與可選擇通道一起定義了一個多路複用、非阻塞 I/O 功能。

Java NIO - 環境設定

本節指導您如何在您的機器上下載和設定 Java。請按照以下步驟設定環境。

Java SE 可從以下連結免費獲得 下載 Java。因此,您可以根據您的作業系統下載一個版本。

按照說明下載 Java 並執行 **.exe** 檔案以在您的機器上安裝 Java。在您的機器上安裝 Java 後,您需要設定環境變數以指向正確的安裝目錄:

為 Windows 2000/XP 設定路徑

假設您已將 Java 安裝在 *c:\Program Files\java\jdk* 目錄下:

  • 右鍵單擊“我的電腦”,然後選擇“屬性”。

  • 在“高階”選項卡下,單擊“環境變數”按鈕。

  • 現在修改“Path”變數,使其還包含 Java 可執行檔案的路徑。例如,如果路徑當前設定為“C:\WINDOWS\SYSTEM32”,則將您的路徑更改為“C:\WINDOWS\SYSTEM32;c:\Program Files\java\jdk\bin”。

為 Windows 95/98/ME 設定路徑

假設您已將 Java 安裝在 *c:\Program Files\java\jdk* 目錄下:

  • 編輯“C:\autoexec.bat”檔案,並在末尾新增以下行
    “SET PATH = %PATH%;C:\Program Files\java\jdk\bin”

為 Linux、UNIX、Solaris、FreeBSD 設定路徑

環境變數 PATH 應設定為指向 Java 二進位制檔案安裝的位置。如果您在執行此操作時遇到問題,請參閱您的 shell 文件。

例如,如果您使用 *bash* 作為您的 shell,則您將在您的“.bashrc”的末尾新增以下行:export PATH = /path/to/java:$PATH

流行的 Java 編輯器

要編寫 Java 程式,您需要一個文字編輯器。市場上還有更多複雜的 IDE 可供使用。但目前,您可以考慮以下其中之一:

  • **記事本** - 在 Windows 機器上,您可以使用任何簡單的文字編輯器,如記事本(推薦用於本教程)、TextPad。

  • **Netbeans** - 是一款開源且免費的 Java IDE,可從 http://www.netbeans.org/index.html 下載。

  • **Eclipse** - 也是一款由 Eclipse 開源社群開發的 Java IDE,可從 https://www.eclipse.org/ 下載。

Java NIO 與 IO

眾所周知,Java NIO 是為了改進傳統的 Java IO API 而引入的。NIO 比 IO 更高效的主要改進是 NIO 中使用的通道資料流模型以及將傳統 IO 任務委託給作業系統。

Java NIO 和 Java IO 之間的區別可以解釋如下:

  • 如前所述,NIO 中使用面向緩衝區和通道的資料流進行 I/O 操作,與 IO 相比,這提供了更快的執行速度和更好的效能。此外,NIO 使用作業系統執行傳統的 I/O 任務,這再次提高了效率。

  • NIO 和 IO 之間的另一個區別是,IO 使用流式資料流,即一次讀取一個位元組,並依賴於將資料物件轉換為位元組以及反之亦然,而 NIO 處理資料塊,即位元組塊。

  • 在 Java IO 中,流物件是單向的,而在 NIO 中,通道是雙向的,這意味著一個通道既可以用於讀取資料,也可以用於寫入資料。

  • IO 中的流式資料流不允許在資料中向前或向後移動。如果需要在從流中讀取的資料中向前或向後移動,則需要先將其快取到緩衝區中。而在 NIO 的情況下,我們使用的是面向緩衝區的設計,這允許在無需快取的情況下訪問資料。

  • NIO API 還支援多執行緒,以便可以非同步地讀取和寫入資料,這樣在執行 I/O 操作時當前執行緒不會被阻塞。這再次使其比傳統的 Java IO API 更高效。

  • 多執行緒的概念是在 Java NIO 中引入 **選擇器** 時引入的,它允許以非同步或非阻塞的方式監聽多個通道的 I/O 事件。

  • NIO 中的多執行緒使其成為非阻塞的,這意味著只有在資料可用時才會請求執行緒讀取或寫入資料,否則執行緒可以在此期間用於其他任務。但在傳統的 Java IO 中這是不可能的,因為它不支援多執行緒,因此它是阻塞的。

  • NIO 允許使用單個執行緒管理多個通道,但代價是解析資料可能比在 Java IO 中從阻塞流中讀取資料稍微複雜一些。因此,如果需要較少的連線但頻寬很高,並且一次傳送大量資料,那麼在這種情況下,Java IO API 可能是最佳選擇。

Java NIO - 通道

描述

顧名思義,通道用作資料從一端流向另一端的方式。在 Java NIO 中,通道在緩衝區和另一端的實體之間起到相同的作用,換句話說,通道用於將資料讀入緩衝區,也用於將資料從緩衝區寫入。

與傳統 Java IO 中使用的流不同,通道是雙向的,即可以讀取也可以寫入。Java NIO 通道支援資料在阻塞和非阻塞模式下的非同步流。

通道的實現

Java NIO 通道主要在以下類中實現:

  • **FileChannel** - 為了從檔案中讀取資料,我們使用檔案通道。檔案通道的物件只能透過呼叫檔案物件上的 getChannel() 方法來建立,因為我們不能直接建立檔案物件。

  • **DatagramChannel** - 資料報通道可以透過 UDP(使用者資料報協議)在網路上讀取和寫入資料。資料報通道的物件可以使用工廠方法建立。

  • **SocketChannel** - 套接字通道可以透過 TCP(傳輸控制協議)在網路上讀取和寫入資料。它也使用工廠方法來建立新物件。

  • **ServerSocketChannel** - 伺服器套接字通道透過 TCP 連線讀取和寫入資料,就像 Web 伺服器一樣。對於每個傳入連線,都會建立一個 SocketChannel。

示例

以下示例從 **C:/Test/temp.txt** 中讀取文字檔案並將內容列印到控制檯。

temp.txt

Hello World!

ChannelDemo.java

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class ChannelDemo {
   public static void main(String args[]) throws IOException {
      RandomAccessFile file = new RandomAccessFile("C:/Test/temp.txt", "r");
      FileChannel fileChannel = file.getChannel();
      ByteBuffer byteBuffer = ByteBuffer.allocate(512);
      while (fileChannel.read(byteBuffer) > 0) {
         // flip the buffer to prepare for get operation
         byteBuffer.flip();
         while (byteBuffer.hasRemaining()) {
            System.out.print((char) byteBuffer.get());
         }
      }
      file.close();
   }
}

輸出

Hello World!

Java NIO - 檔案通道

描述

如前所述,Java NIO 通道的 FileChannel 實現用於訪問檔案的元資料屬性,包括建立、修改、大小等。此外,檔案通道是多執行緒的,這再次使得 Java NIO 比 Java IO 更高效。

總的來說,我們可以說 FileChannel 是一個連線到檔案的通道,透過它可以從檔案讀取資料,也可以將資料寫入檔案。FileChannel 的另一個重要特性是它不能設定為非阻塞模式,並且始終以阻塞模式執行。

我們不能直接獲取檔案通道物件,檔案通道的物件可以透過以下方式獲取:

  • **getChannel()** - FileInputStream、FileOutputStream 或 RandomAccessFile 上的方法。

  • **open()** - 檔案通道的方法,它預設開啟通道。

檔案通道的物件型別取決於建立物件時呼叫的類型別,即如果透過呼叫 FileInputStream 的 getchannel 方法建立物件,則檔案通道將以只讀方式開啟,並在嘗試寫入時丟擲 NonWritableChannelException。

示例

以下示例演示瞭如何讀取和寫入 Java NIO FileChannel 的資料。

以下示例從 **C:/Test/temp.txt** 中讀取文字檔案並將內容列印到控制檯。

temp.txt

Hello World!

FileChannelDemo.java

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.HashSet;
import java.util.Set;

public class FileChannelDemo {
   public static void main(String args[]) throws IOException {
      //append the content to existing file 
      writeFileChannel(ByteBuffer.wrap("Welcome to TutorialsPoint".getBytes()));
      //read the file
      readFileChannel();
   }
   public static void readFileChannel() throws IOException {
      RandomAccessFile randomAccessFile = new RandomAccessFile("C:/Test/temp.txt",
      "rw");
      FileChannel fileChannel = randomAccessFile.getChannel();
      ByteBuffer byteBuffer = ByteBuffer.allocate(512);
      Charset charset = Charset.forName("US-ASCII");
      while (fileChannel.read(byteBuffer) > 0) {
         byteBuffer.rewind();
         System.out.print(charset.decode(byteBuffer));
         byteBuffer.flip();
      }
      fileChannel.close();
      randomAccessFile.close();
   }
   public static void writeFileChannel(ByteBuffer byteBuffer)throws IOException {
      Set<StandardOpenOption> options = new HashSet<>();
      options.add(StandardOpenOption.CREATE);
      options.add(StandardOpenOption.APPEND);
      Path path = Paths.get("C:/Test/temp.txt");
      FileChannel fileChannel = FileChannel.open(path, options);
      fileChannel.write(byteBuffer);
      fileChannel.close();
   }
}

輸出

Hello World! Welcome to TutorialsPoint

Java NIO - 資料報通道

Java NIO 資料報用作通道,可以透過無連線協議傳送和接收 UDP 資料包。預設情況下,資料報通道是阻塞的,但它可以用於非阻塞模式。為了使其成為非阻塞的,我們可以使用 configureBlocking(false) 方法。資料報通道可以透過呼叫其名為 **open()** 的靜態方法之一來開啟,該方法還可以將 IP 地址作為引數,以便它可以用於多播。

與 FileChannel 類似,資料報通道預設未連線,為了使其連線,我們必須顯式呼叫其 connect() 方法。但是,資料報通道不必連線即可使用 send 和 receive 方法,但必須連線才能使用 read 和 write 方法,因為這些方法不接受或返回套接字地址。

我們可以透過呼叫其 **isConnected()** 方法來檢查資料報通道的連線狀態。一旦連線,資料報通道將保持連線,直到它斷開連線或關閉。資料報通道是執行緒安全的,並且同時支援多執行緒和併發。

資料報通道的重要方法

  • **bind(SocketAddress local)** - 此方法用於將資料報通道的套接字繫結到作為此方法引數提供的本地地址。

  • connect(SocketAddress remote) − 此方法用於將套接字連線到遠端地址。

  • disconnect() − 此方法用於斷開套接字與遠端地址的連線。

  • getRemoteAddress() − 此方法返回通道的套接字連線到的遠端位置的地址。

  • isConnected() − 如前所述,此方法返回資料報通道連線狀態,即它是否已連線。

  • open() 和 open(ProtocolFamily family) − open 方法用於為單個地址開啟資料報通道,而帶引數的 open 方法則為表示為協議族的多個地址開啟通道。

  • read(ByteBuffer dst) − 此方法用於透過資料報通道從給定緩衝區讀取資料。

  • receive(ByteBuffer dst) − 此方法用於透過此通道接收資料報。

  • send(ByteBuffer src, SocketAddress target) − 此方法用於透過此通道傳送資料報。

示例

以下示例演示瞭如何從 Java NIO DataGramChannel 傳送資料。

伺服器:DatagramChannelServer.java

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

public class DatagramChannelServer {
   public static void main(String[] args) throws IOException {
      DatagramChannel server = DatagramChannel.open();
      InetSocketAddress iAdd = new InetSocketAddress("localhost", 8989);
      server.bind(iAdd);
      System.out.println("Server Started: " + iAdd);
      ByteBuffer buffer = ByteBuffer.allocate(1024);
      //receive buffer from client.
      SocketAddress remoteAdd = server.receive(buffer);
      //change mode of buffer
      buffer.flip();
      int limits = buffer.limit();
      byte bytes[] = new byte[limits];
      buffer.get(bytes, 0, limits);
      String msg = new String(bytes);
      System.out.println("Client at " + remoteAdd + "  sent: " + msg);
      server.send(buffer,remoteAdd);
      server.close();
   }
}

輸出

Server Started: localhost/127.0.0.1:8989

客戶端:DatagramChannelClient.java

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

public class DatagramChannelClient {
   public static void main(String[] args) throws IOException {
      DatagramChannel client = null;
      client = DatagramChannel.open();

      client.bind(null);

      String msg = "Hello World!";
      ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
      InetSocketAddress serverAddress = new InetSocketAddress("localhost",
        8989);

      client.send(buffer, serverAddress);
      buffer.clear();
      client.receive(buffer);
      buffer.flip();
    
      client.close();
   }
}

輸出

執行客戶端將在伺服器上列印以下輸出。

Server Started: localhost/127.0.0.1:8989
Client at /127.0.0.1:64857  sent: Hello World!

Java NIO - 套接字通道

Java NIO 套接字通道是一種可選擇的型別通道,這意味著它可以使用選擇器進行多路複用,用於連線套接字的面向流的資料流。可以透過呼叫其靜態open()方法建立套接字通道,前提是任何預先存在的套接字不存在。套接字通道透過呼叫 open 方法建立,但尚未連線。為了連線套接字通道,需要呼叫connect()方法。這裡需要說明的一點是,如果通道未連線且嘗試進行任何 I/O 操作,則此通道將丟擲 NotYetConnectedException。因此,必須確保在執行任何 IO 操作之前通道已連線。一旦通道連線,它將保持連線狀態,直到它關閉。可以透過呼叫其isConnected方法確定套接字通道的狀態。

可以透過呼叫其finishConnect()方法完成套接字通道的連線。是否正在進行連線操作可以透過呼叫 isConnectionPending 方法來確定。預設情況下,套接字通道支援非阻塞連線。它還支援非同步關閉,這類似於 Channel 類中指定的非同步關閉操作。

套接字通道可安全地供多個併發執行緒使用。它們支援併發讀取和寫入,儘管在任何給定時間最多隻能有一個執行緒讀取,最多隻能有一個執行緒寫入。connect 和 finishConnect 方法彼此之間是相互同步的,並且在其中一個方法的呼叫正在進行時嘗試啟動讀取或寫入操作將阻塞,直到該呼叫完成。

套接字通道的重要方法

  • bind(SocketAddress local) − 此方法用於將套接字通道繫結到作為此方法引數提供的本地地址。

  • connect(SocketAddress remote) − 此方法用於將套接字連線到遠端地址。

  • finishConnect() − 此方法用於完成連線套接字通道的過程。

  • getRemoteAddress() − 此方法返回通道的套接字連線到的遠端位置的地址。

  • isConnected() − 如前所述,此方法返回套接字通道連線狀態,即它是否已連線。

  • open() 和 open((SocketAddress remote) − open 方法用於為未指定地址開啟套接字通道,而帶引數的 open 方法則為指定的遠端地址開啟通道並連線到它。此便捷方法的工作方式類似於呼叫 open() 方法,對生成的套接字通道呼叫 connect 方法,將 remote 傳遞給它,然後返回該通道。

  • read(ByteBuffer dst) − 此方法用於透過套接字通道從給定緩衝區讀取資料。

  • isConnectionPending() − 此方法指示此通道上是否正在進行連線操作。

示例

以下示例演示瞭如何從 Java NIO SocketChannel 傳送資料。

C:/Test/temp.txt

Hello World!

客戶端:SocketChannelClient.java

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;

public class SocketChannelClient {
   public static void main(String[] args) throws IOException {
      ServerSocketChannel serverSocket = null;
      SocketChannel client = null;
      serverSocket = ServerSocketChannel.open();
      serverSocket.socket().bind(new InetSocketAddress(9000));
      client = serverSocket.accept();
      System.out.println("Connection Set:  " + client.getRemoteAddress());
      Path path = Paths.get("C:/Test/temp1.txt");
      FileChannel fileChannel = FileChannel.open(path, 
         EnumSet.of(StandardOpenOption.CREATE, 
            StandardOpenOption.TRUNCATE_EXISTING,
            StandardOpenOption.WRITE)
         );      
      ByteBuffer buffer = ByteBuffer.allocate(1024);
      while(client.read(buffer) > 0) {
         buffer.flip();
         fileChannel.write(buffer);
         buffer.clear();
      }
      fileChannel.close();
      System.out.println("File Received");
      client.close();
   }
}

輸出

執行客戶端在伺服器啟動之前不會列印任何內容。


伺服器:SocketChannelServer.java

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.nio.file.Paths;

public class SocketChannelServer {
   public static void main(String[] args) throws IOException {
      SocketChannel server = SocketChannel.open();
      SocketAddress socketAddr = new InetSocketAddress("localhost", 9000);
      server.connect(socketAddr);

      Path path = Paths.get("C:/Test/temp.txt");
      FileChannel fileChannel = FileChannel.open(path);
      ByteBuffer buffer = ByteBuffer.allocate(1024);
      while(fileChannel.read(buffer) > 0) {
         buffer.flip();
         server.write(buffer);
         buffer.clear();
      }
      fileChannel.close();
      System.out.println("File Sent");
      server.close();
   }
}

輸出

執行伺服器將列印以下內容。

Connection Set:  /127.0.0.1:49558
File Received

Java NIO - ServerSocket Channel

Java NIO 伺服器套接字通道再次是一種可選擇的型別通道,用於連線套接字的面向流的資料流。可以透過呼叫其靜態open()方法建立伺服器套接字通道,前提是任何預先存在的套接字不存在。伺服器套接字通道透過呼叫 open 方法建立,但尚未繫結。為了繫結套接字通道,需要呼叫bind()方法。

這裡需要說明的一點是,如果通道未繫結且嘗試進行任何 I/O 操作,則此通道將丟擲 NotYetBoundException。因此,必須確保在執行任何 IO 操作之前通道已繫結。

透過呼叫 ServerSocketChannel.accept() 方法監聽伺服器套接字通道的傳入連線。當 accept() 方法返回時,它將返回一個帶有傳入連線的 SocketChannel。因此,accept() 方法將阻塞,直到傳入連線到達。如果通道處於非阻塞模式,則如果沒有任何掛起的連線,accept 方法將立即返回 null。否則,它將無限期地阻塞,直到新的連線可用或發生 I/O 錯誤。

新通道的套接字最初未繫結;在可以接受連線之前,必須透過其套接字的 bind 方法之一將其繫結到特定地址。此外,新通道是透過呼叫系統範圍內的預設 SelectorProvider 物件的 openServerSocketChannel 方法建立的。

像套接字通道一樣,伺服器套接字通道可以使用read()方法讀取資料。首先分配緩衝區。從 ServerSocketChannel 讀取的資料儲存到緩衝區中。其次,我們呼叫 ServerSocketChannel.read() 方法,它將資料從 ServerSocketChannel 讀取到緩衝區中。read() 方法的整數值返回寫入緩衝區的位元組數。

類似地,可以使用write()方法將資料寫入伺服器套接字通道,使用緩衝區作為引數。通常在 while 迴圈中使用 write 方法,因為需要重複 write() 方法,直到 Buffer 沒有更多可寫入的位元組。

套接字通道的重要方法

  • bind(SocketAddress local) − 此方法用於將套接字通道繫結到作為此方法引數提供的本地地址。

  • accept() − 此方法用於接受與此通道的套接字建立的連線。

  • connect(SocketAddress remote) − 此方法用於將套接字連線到遠端地址。

  • finishConnect() − 此方法用於完成連線套接字通道的過程。

  • getRemoteAddress() − 此方法返回通道的套接字連線到的遠端位置的地址。

  • isConnected() − 如前所述,此方法返回套接字通道連線狀態,即它是否已連線。

  • open() − open 方法用於為未指定地址開啟套接字通道。此便捷方法的工作方式類似於呼叫 open() 方法,對生成的伺服器套接字通道呼叫 connect 方法,將 remote 傳遞給它,然後返回該通道。

  • read(ByteBuffer dst) − 此方法用於透過套接字通道從給定緩衝區讀取資料。

  • setOption(SocketOption<T> name, T value) − 此方法設定套接字選項的值。

  • socket() − 此方法檢索與此通道關聯的伺服器套接字。

  • validOps() − 此方法返回一個操作集,標識此通道支援的操作。伺服器套接字通道僅支援接受新連線,因此此方法返回 SelectionKey.OP_ACCEPT。

示例

以下示例演示瞭如何從 Java NIO ServerSocketChannel 傳送資料。

C:/Test/temp.txt

Hello World!

客戶端:SocketChannelClient.java

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;

public class SocketChannelClient {
   public static void main(String[] args) throws IOException {
      ServerSocketChannel serverSocket = null;
      SocketChannel client = null;
      serverSocket = ServerSocketChannel.open();
      serverSocket.socket().bind(new InetSocketAddress(9000));
      client = serverSocket.accept();
      System.out.println("Connection Set:  " + client.getRemoteAddress());
      Path path = Paths.get("C:/Test/temp1.txt");
      FileChannel fileChannel = FileChannel.open(path, 
         EnumSet.of(StandardOpenOption.CREATE, 
            StandardOpenOption.TRUNCATE_EXISTING,
            StandardOpenOption.WRITE)
         );      
      ByteBuffer buffer = ByteBuffer.allocate(1024);
      while(client.read(buffer) > 0) {
         buffer.flip();
         fileChannel.write(buffer);
         buffer.clear();
      }
      fileChannel.close();
      System.out.println("File Received");
      client.close();
   }
}

輸出

執行客戶端在伺服器啟動之前不會列印任何內容。


伺服器:SocketChannelServer.java

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.nio.file.Paths;

public class SocketChannelServer {
   public static void main(String[] args) throws IOException {
      SocketChannel server = SocketChannel.open();
      SocketAddress socketAddr = new InetSocketAddress("localhost", 9000);
      server.connect(socketAddr);
      Path path = Paths.get("C:/Test/temp.txt");
      FileChannel fileChannel = FileChannel.open(path);
      ByteBuffer buffer = ByteBuffer.allocate(1024);
      while(fileChannel.read(buffer) > 0) {
         buffer.flip();
         server.write(buffer);
         buffer.clear();
      }
      fileChannel.close();
      System.out.println("File Sent");
      server.close();
   }
}

輸出

執行伺服器將列印以下內容。

Connection Set:  /127.0.0.1:49558
File Received

Java NIO - 分散讀取

眾所周知,與 Java 的傳統 IO API 相比,Java NIO 是一個更最佳化的資料 IO 操作 API。Java NIO 提供的另一個額外支援是讀取/寫入多個緩衝區到通道的資料。這種多讀取和寫入支援被稱為 Scatter 和 Gather,在讀取資料的情況下,資料從單個通道分散到多個緩衝區,而在寫入資料的情況下,資料從多個緩衝區收集到單個通道。

為了實現從通道進行這種多讀取和寫入,Java NIO 提供了 ScatteringByteChannel 和 GatheringByteChannel API 來讀取和寫入資料,如下例所示。

ScatteringByteChannel

從多個通道讀取 − 在此,我們從單個通道讀取資料到多個緩衝區。為此,分配多個緩衝區並將其新增到緩衝區型別陣列中。然後將此陣列作為引數傳遞給 ScatteringByteChannel read() 方法,該方法然後按緩衝區在陣列中出現的順序從通道寫入資料。緩衝區滿後,通道繼續填充下一個緩衝區。

以下示例演示瞭如何在 Java NIO 中執行資料的散佈。

C:/Test/temp.txt

Hello World!
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ScatteringByteChannel;

public class ScatterExample {	
   private static String FILENAME = "C:/Test/temp.txt";
   public static void main(String[] args) {
      ByteBuffer bLen1 = ByteBuffer.allocate(1024);
      ByteBuffer bLen2 = ByteBuffer.allocate(1024);
      FileInputStream in;
      try {
         in = new FileInputStream(FILENAME);
         ScatteringByteChannel scatter = in.getChannel();
         scatter.read(new ByteBuffer[] {bLen1, bLen2});
         bLen1.position(0);
         bLen2.position(0);
         int len1 = bLen1.asIntBuffer().get();
         int len2 = bLen2.asIntBuffer().get();
         System.out.println("Scattering : Len1 = " + len1);
         System.out.println("Scattering : Len2 = " + len2);
      } 
      catch (FileNotFoundException exObj) {
         exObj.printStackTrace();
      }
      catch (IOException ioObj) {
         ioObj.printStackTrace();
      }
   }
}

輸出

Scattering : Len1 = 1214606444
Scattering : Len2 = 0

最後可以得出結論,Java NIO 中的 scatter/gather 方法在正確使用時是一種最佳化且多工的方法。它允許您將分離您讀取的資料到多個儲存桶或將分散的資料塊組裝成一個整體的繁瑣工作委託給作業系統。毫無疑問,這節省了時間並透過避免緩衝區複製更有效地利用了作業系統,並減少了需要編寫和除錯的程式碼量。

Java NIO - 聚集寫入

眾所周知,與 Java 的傳統 IO API 相比,Java NIO 是一個更最佳化的資料 IO 操作 API。Java NIO 提供的另一個額外支援是讀取/寫入多個緩衝區到通道的資料。這種多讀取和寫入支援被稱為 Scatter 和 Gather,在讀取資料的情況下,資料從單個通道分散到多個緩衝區,而在寫入資料的情況下,資料從多個緩衝區收集到單個通道。

為了實現從通道進行這種多讀取和寫入,Java NIO 提供了 ScatteringByteChannel 和 GatheringByteChannel API 來讀取和寫入資料,如下例所示。

GatheringByteChannel

寫入多個通道 − 在此,我們從多個緩衝區寫入資料到單個通道。為此,再次分配多個緩衝區並將其新增到緩衝區型別陣列中。然後將此陣列作為引數傳遞給 GatheringByteChannel write() 方法,該方法然後按緩衝區在陣列中出現的順序從多個緩衝區寫入資料。這裡需要記住的一點是,僅寫入緩衝區位置和限制之間的資料。

以下示例演示瞭如何在 Java NIO 中執行資料收集。

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.GatheringByteChannel;

public class GatherExample {
   private static String FILENAME = "C:/Test/temp.txt";
   public static void main(String[] args) {
      String stream1 = "Gather data stream first";
      String stream2 = "Gather data stream second";
      ByteBuffer bLen1 = ByteBuffer.allocate(1024);
      ByteBuffer bLen2 = ByteBuffer.allocate(1024);
      // Next two buffer hold the data we want to write
      ByteBuffer bstream1 = ByteBuffer.wrap(stream1.getBytes());
      ByteBuffer bstream2 = ByteBuffer.wrap(stream2.getBytes());
      int len1 = stream1.length();
      int len2 = stream2.length();
      // Writing length(data) to the Buffer
      bLen1.asIntBuffer().put(len1);
      bLen2.asIntBuffer().put(len2);
      System.out.println("Gathering : Len1 = " + len1);
      System.out.println("Gathering : Len2 = " + len2);
      // Write data to the file
      try { 
         FileOutputStream out = new FileOutputStream(FILENAME);
         GatheringByteChannel gather = out.getChannel();						
         gather.write(new ByteBuffer[] {bLen1, bLen2, bstream1, bstream2});
         out.close();
         gather.close();
      }
      catch (FileNotFoundException exObj) {
         exObj.printStackTrace();
      }
      catch(IOException ioObj) {
         ioObj.printStackTrace();
      }
   }
}

輸出

Gathering : Len1 = 24
Gathering : Len2 = 25

最後可以得出結論,Java NIO 中的 scatter/gather 方法在正確使用時是一種最佳化且多工的方法。它允許您將分離您讀取的資料到多個儲存桶或將分散的資料塊組裝成一個整體的繁瑣工作委託給作業系統。毫無疑問,這節省了時間並透過避免緩衝區複製更有效地利用了作業系統,並減少了需要編寫和除錯的程式碼量。

Java NIO - 緩衝區

Java NIO 中的緩衝區可以被視為一個簡單的物件,充當資料塊的固定大小容器,可用於將資料寫入通道或從通道讀取資料,以便緩衝區充當通道的端點。

它提供了一組方法,使處理記憶體塊以讀取和寫入通道的資料更加方便。

與經典 IO 相比,緩衝區使 NIO 包更有效率和更快,因為在 IO 的情況下,資料以流的形式處理,不支援資料的非同步和併發流。此外,IO 不允許以塊或位元組組的形式執行資料。

定義 Java NIO 緩衝區的主要引數可以定義為 -

  • 容量 - 緩衝區中可以儲存的最大資料/位元組量。緩衝區的容量無法更改。緩衝區滿後,應在寫入之前將其清空。

  • 限制 - 限制根據緩衝區的模式具有含義,即在緩衝區的寫入模式下,限制等於容量,這意味著可以寫入緩衝區的最大資料量。而在緩衝區的讀取模式下,限制表示可以從緩衝區讀取的資料量限制。

  • 位置 - 指向緩衝區中游標的當前位置。最初在建立緩衝區時設定為 0,換句話說,它是下一個要讀取或寫入的元素的索引,它會由 get() 和 put() 方法自動更新。

  • 標記 - 在緩衝區中標記位置的書籤。當呼叫 mark() 方法時,會記錄當前位置,當呼叫 reset() 時,會恢復標記的位置。

緩衝區型別

Java NIO 緩衝區可以根據緩衝區處理的資料型別分類為以下變體:

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

緩衝區的重要方法

如前所述,緩衝區充當記憶體物件,它提供一組方法,使處理記憶體塊更加方便。以下是緩衝區的重要方法:

  • allocate(int capacity) - 此方法用於分配一個新的緩衝區,容量作為引數。如果傳遞的容量為負整數,則 Allocate 方法會丟擲 IllegalArgumentException。

  • read() 和 put() - 通道的 read 方法用於將資料從通道寫入緩衝區,而 put 是緩衝區的方法,用於將資料寫入緩衝區。

  • flip() - flip 方法將緩衝區的模式從寫入模式切換到讀取模式。它還將位置重置為 0,並將限制設定為寫入時位置所在的位置。

  • write() 和 get() - 通道的 write 方法用於將資料從緩衝區寫入通道,而 get 是緩衝區的方法,用於從緩衝區讀取資料。

  • rewind() - rewind 方法用於需要重新讀取時,因為它將位置重置為零,並且不更改限制的值。

  • clear() 和 compact() - clear 和 compact 這兩種方法都用於將緩衝區從讀取模式更改為寫入模式。clear() 方法將位置設定為零,並將限制設定為容量,在此方法中,緩衝區中的資料不會被清除,只有標記會被重新初始化。

    另一方面,compact() 方法用於當仍有一些未讀取的資料並且我們仍然使用緩衝區的寫入模式時,在這種情況下,compact 方法會將所有未讀取的資料複製到緩衝區的開頭,並將位置設定為最後一個未讀取元素的後面。limit 屬性仍然設定為容量。

  • mark() 和 reset() - 顧名思義,mark 方法用於在緩衝區中標記任何特定位置,而 reset 將位置恢復到標記的位置。

示例

以下示例顯示了上述定義方法的實現。

import java.nio.ByteBuffer;
import java.nio.CharBuffer;

public class BufferDemo {
   public static void main (String [] args) {
      //allocate a character type buffer.
      CharBuffer buffer = CharBuffer.allocate(10);
      String text = "bufferDemo";
      System.out.println("Input text: " + text);
      for (int i = 0; i < text.length(); i++) {
         char c = text.charAt(i);
         //put character in buffer.
		 buffer.put(c);
      }
      int buffPos = buffer.position();
      System.out.println("Position after data is written into buffer: " + buffPos);
      buffer.flip();
      System.out.println("Reading buffer contents:");
      while (buffer.hasRemaining()) {
         System.out.println(buffer.get());                   
      }
      //set the position of buffer to 5.
      buffer.position(5);
      //sets this buffer's mark at its position
      buffer.mark();
      //try to change the position
      buffer.position(6);
      //calling reset method to restore to the position we marked.
      //reset() raise InvalidMarkException if either the new position is less
      //than the position marked or merk has not been setted.
      buffer.reset();
      System.out.println("Restored buffer position : " + buffer.position());
   }
}

輸出

Input text: bufferDemo
Position after data is written into buffer: 10
Reading buffer contents:
b
u
f
f
e
r
D
e
m
o
Restored buffer position : 5

Java NIO - 選擇器

眾所周知,Java NIO 支援與通道和緩衝區之間的多個事務。因此,為了檢查一個或多個 NIO 通道,並確定哪些通道已準備好進行資料事務(即讀取或寫入),Java NIO 提供了 Selector。

使用 Selector,我們可以建立一個執行緒來了解哪個通道已準備好進行資料寫入和讀取,並可以處理該特定通道。

我們可以透過呼叫其靜態方法 open() 獲取選擇器例項。開啟選擇器後,我們必須在其上註冊一個非阻塞模式通道,該通道會返回一個 SelectionKey 例項。

SelectionKey 基本上是可以在通道上執行的操作的集合,或者我們可以說我們可以藉助選擇鍵瞭解通道的狀態。

選擇鍵表示的主要操作或通道狀態如下:

  • SelectionKey.OP_CONNECT - 準備連線到伺服器的通道。

  • SelectionKey.OP_ACCEPT - 準備接受傳入連線的通道。

  • SelectionKey.OP_READ - 準備讀取資料的通道。

  • SelectionKey.OP_WRITE - 準備寫入資料的通道。

註冊後獲得的選擇鍵有一些重要的方法,如下所示:

  • attach() - 此方法用於將物件附加到鍵。將物件附加到通道的主要目的是識別相同的通道。

  • attachment() - 此方法用於從通道中保留附加的物件。

  • channel() - 此方法用於獲取為其建立特定鍵的通道。

  • selector() - 此方法用於獲取為其建立特定鍵的選擇器。

  • isValid() - 此方法返回鍵是否有效。

  • isReadable() - 此方法表明鍵的通道是否已準備好讀取。

  • isWritable() - 此方法表明鍵的通道是否已準備好寫入。

  • isAcceptable() - 此方法表明鍵的通道是否已準備好接受傳入連線。

  • isConnectable() - 此方法測試此鍵的通道是否已完成或未能完成其套接字連線操作。

  • isAcceptable() - 此方法測試此鍵的通道是否已準備好接受新的套接字連線。

  • interestOps() - 此方法檢索此鍵的興趣集。

  • readyOps() - 此方法檢索就緒集,即通道已準備好的操作集。

我們可以透過呼叫其靜態方法 select() 從選擇器中選擇一個通道。選擇器的 Select 方法被過載為:

  • select() - 此方法阻塞當前執行緒,直到至少有一個通道已準備好進行其註冊的事件。

  • select(long timeout) - 此方法與 select() 執行相同的操作,但它最多阻塞執行緒 timeout 毫秒(引數)。

  • selectNow() - 此方法根本不阻塞。它會立即返回任何已準備好的通道。

此外,為了離開一個呼叫 select 方法的阻塞執行緒,可以在選擇器例項中呼叫 wakeup() 方法,之後在 select() 內部等待的執行緒將立即返回。

最後,我們可以透過呼叫 close() 方法關閉選擇器,該方法還會使與此選擇器註冊的所有 SelectionKey 例項失效,並關閉選擇器。

示例

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class SelectorDemo {
   public static void main(String[] args) throws IOException {
      String demo_text = "This is a demo String";	
      Selector selector = Selector.open();
      ServerSocketChannel serverSocket = ServerSocketChannel.open();
      serverSocket.bind(new InetSocketAddress("localhost", 5454));
      serverSocket.configureBlocking(false);
      serverSocket.register(selector, SelectionKey.OP_ACCEPT);
      ByteBuffer buffer = ByteBuffer.allocate(256);
      while (true) {
         selector.select();
         Set<SelectionKey> selectedKeys = selector.selectedKeys();
         Iterator<SelectionKey> iter = selectedKeys.iterator();
         while (iter.hasNext()) {
            SelectionKey key = iter.next();
            int interestOps = key.interestOps();
            System.out.println(interestOps);
            if (key.isAcceptable()) {
               SocketChannel client = serverSocket.accept();
               client.configureBlocking(false);
               client.register(selector, SelectionKey.OP_READ);
            }
            if (key.isReadable()) {
               SocketChannel client = (SocketChannel) key.channel();
               client.read(buffer);
               if (new String(buffer.array()).trim().equals(demo_text)) {
                  client.close();
                  System.out.println("Not accepting client messages anymore");
               }
               buffer.flip();
               client.write(buffer);
               buffer.clear();
            }
            iter.remove();
         }
      }
   }
}

Java NIO - 管道

在 Java NIO 中,管道是一個用於在兩個執行緒之間寫入和讀取資料的元件。管道主要由兩個負責資料傳播的通道組成。

在兩個組成通道中,一個稱為接收器通道,主要用於寫入資料;另一個是源通道,其主要目的是從接收器通道讀取資料。

在資料寫入和讀取期間,資料同步保持有序,因為必須確保資料必須以與寫入管道的相同順序讀取。

必須注意,管道中的資料流是單向的,即資料只能寫入接收器通道,並且只能從源通道讀取。

在 Java NIO 中,管道被定義為一個抽象類,主要有三個方法,其中兩個是抽象的。

Pipe 類的使用方法

  • open() - 此方法用於獲取 Pipe 的例項,或者我們可以說透過呼叫此方法建立管道。

  • sink() - 此方法返回 Pipe 的接收器通道,該通道用於透過呼叫其 write 方法寫入資料。

  • source() - 此方法返回 Pipe 的源通道,該通道用於透過呼叫其 read 方法讀取資料。

示例

以下示例顯示了 Java NIO 管道的實現。

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;

public class PipeDemo {
   public static void main(String[] args) throws IOException {
      //An instance of Pipe is created
      Pipe pipe = Pipe.open();
      // gets the pipe's sink channel
      Pipe.SinkChannel skChannel = pipe.sink();
      String testData = "Test Data to Check java NIO Channels Pipe.";
      ByteBuffer buffer = ByteBuffer.allocate(512);
      buffer.clear();
      buffer.put(testData.getBytes());
      buffer.flip();
      //write data into sink channel.
      while(buffer.hasRemaining()) {
         skChannel.write(buffer);
      }
      //gets  pipe's source channel
      Pipe.SourceChannel sourceChannel = pipe.source();
      buffer = ByteBuffer.allocate(512);
      //write data into console     
      while(sourceChannel.read(buffer) > 0){
         //limit is set to current position and position is set to zero
         buffer.flip();
         while(buffer.hasRemaining()){
            char ch = (char) buffer.get();
            System.out.print(ch);
         }
         //position is set to zero and limit is set to capacity to clear the buffer.
         buffer.clear();
      }
   }
}

輸出

Test Data to Check java NIO Channels Pipe.

假設我們有一個文字檔案 c:/test.txt,其內容如下。此檔案將用作我們示例程式的輸入。

Java NIO - 路徑

顧名思義,路徑是在檔案系統中實體(例如檔案或目錄)的特定位置,以便人們可以在該特定位置搜尋和訪問它。

從技術的角度來看,在 Java 中,Path 是一個介面,在 Java 版本 7 期間在 Java NIO 檔案包中引入,它是檔案系統中位置的表示。由於路徑介面位於 Java NIO 包中,因此其限定名稱為 java.nio.file.Path。

通常,實體的路徑可以分為兩種型別:一種是絕對路徑,另一種是相對路徑。顧名思義,絕對路徑是從根到實體所在位置的地址,而相對路徑是相對於其他路徑的位置地址。路徑在其定義中使用分隔符,Windows 使用“\”,Unix 作業系統使用“/”。

為了獲取 Path 的例項,我們可以使用 java.nio.file.Paths 類的靜態方法 get()。此方法將路徑字串或一系列字串(當連線在一起時形成路徑字串)轉換為 Path 例項。如果傳遞的引數包含非法字元,此方法還會丟擲執行時 InvalidPathException。

如上所述,絕對路徑是透過傳遞根元素和定位檔案所需的完整目錄列表來檢索的。而相對路徑可以透過組合基本路徑和相對路徑來檢索。以下示例將說明兩種路徑的檢索。

示例

package com.java.nio;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.file.FileSystem;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
public class PathDemo {
   public static void main(String[] args) throws IOException {
      Path relative = Paths.get("file2.txt");
      System.out.println("Relative path: " + relative);
      Path absolute = relative.toAbsolutePath();
      System.out.println("Absolute path: " + absolute);
   }
}

到目前為止,我們已經瞭解了什麼是路徑介面、為什麼需要它以及如何訪問它。現在,我們將瞭解 Path 介面為我們提供了哪些重要方法。

Path 介面的重要方法

  • getFileName() - 返回建立此物件的

  • getName() - 將此路徑的名稱元素作為 Path 物件返回。

  • getNameCount() - 返回路徑中名稱元素的數量。

  • subpath() - 返回一個相對路徑,它是此路徑的名稱元素的子序列。

  • getParent() - 返回父路徑,如果此路徑沒有父路徑,則返回 null。

  • getRoot() - 將此路徑的根元件作為 Path 物件返回,如果此路徑沒有根元件,則返回 null。

  • toAbsolutePath() - 返回一個表示此路徑的絕對路徑的 Path 物件。

  • toRealPath() - 返回現有檔案的真實路徑。

  • toFile() - 返回一個表示此路徑的 File 物件。

  • normalize() - 返回一個路徑,該路徑是此路徑,其中已消除冗餘名稱元素。

  • compareTo(Path other) - 按字典順序比較兩個抽象路徑。如果引數等於此路徑,此方法返回零;如果此路徑按字典順序小於引數,則返回小於零的值;如果此路徑按字典順序大於引數,則返回大於零的值。

  • endsWith(Path other) − 測試此路徑是否以給定路徑結尾。如果給定路徑有 N 個元素,並且沒有根元件,並且此路徑有 N 個或更多元素,那麼如果每個路徑的最後 N 個元素(從最遠離根的元素開始)相等,則此路徑以給定路徑結尾。

  • endsWith(String other) − 測試此路徑是否以一個 Path 結尾,該 Path 是透過以 endsWith(Path) 方法中指定的方式轉換給定的路徑字串構建的。

示例

以下示例說明了上面提到的 Path 介面的不同方法 −

package com.java.nio;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.file.FileSystem;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
public class PathDemo {
   public static void main(String[] args) throws IOException {
      Path path = Paths.get("D:/workspace/ContentW/Saurav_CV.docx");
      FileSystem fs =  path.getFileSystem();
      System.out.println(fs.toString());
      System.out.println(path.isAbsolute());
      System.out.println(path.getFileName());
      System.out.println(path.toAbsolutePath().toString());
      System.out.println(path.getRoot());
      System.out.println(path.getParent());
      System.out.println(path.getNameCount());
      System.out.println(path.getName(0));
      System.out.println(path.subpath(0, 2));
      System.out.println(path.toString());
      System.out.println(path.getNameCount());
      Path realPath = path.toRealPath(LinkOption.NOFOLLOW_LINKS);
      System.out.println(realPath.toString());
      String originalPath = "d:\\data\\projects\\a-project\\..\\another-project";
      Path path1 = Paths.get(originalPath);
      Path path2 = path1.normalize();
      System.out.println("path2 = " + path2);
   }
}

Java NIO - 檔案

Java NIO 包提供了一個名為 Files 的實用程式 API,它主要用於使用其靜態方法操作檔案和目錄,這些方法大多作用於 Path 物件。

如 Path 教程中所述,Path 介面是在 Java 7 版本中引入 Java NIO 包的 file 包中的。因此,本教程也是針對同一個 File 包。

此類僅包含對檔案、目錄或其他型別檔案進行操作的靜態方法。在大多數情況下,此處定義的方法將委託給關聯的檔案系統提供程式來執行檔案操作。

Files 類中定義了許多方法,也可以從 Java 文件中讀取。在本教程中,我們嘗試涵蓋了 Java NIO Files 類所有方法中的一些重要方法。

Files 類的重要方法。

以下是 Java NIO Files 類中定義的重要方法。

  • createFile(Path filePath, FileAttribute attrs) − Files 類提供此方法,使用指定的 Path 建立檔案。

示例

package com.java.nio;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class CreateFile {
   public static void main(String[] args) {
      //initialize Path object
      Path path = Paths.get("D:file.txt");
      //create file
      try {
         Path createdFilePath = Files.createFile(path);
         System.out.println("Created a file at : "+createdFilePath);
      } 
      catch (IOException e) {
         e.printStackTrace();
      }
   }
}

輸出

Created a file at : D:\data\file.txt
  • copy(InputStream in, Path target, CopyOption… options) − 此方法用於將指定輸入流中的所有位元組複製到指定目標檔案,並返回讀取或寫入的位元組數作為長整型值。此引數的 LinkOption 具有以下值 −

    • COPY_ATTRIBUTES − 將屬性複製到新檔案,例如上次修改時間屬性。

    • REPLACE_EXISTING − 如果檔案存在,則替換現有檔案。

    • NOFOLLOW_LINKS − 如果檔案是符號連結,則複製連結本身,而不是連結的目標。

示例

package com.java.nio;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
public class WriteFile {
   public static void main(String[] args) {
      Path sourceFile = Paths.get("D:file.txt");
      Path targetFile = Paths.get("D:fileCopy.txt");
      try {
         Files.copy(sourceFile, targetFile,
         StandardCopyOption.REPLACE_EXISTING);
      }
      catch (IOException ex) {
         System.err.format("I/O Error when copying file");
      }
      Path wiki_path = Paths.get("D:fileCopy.txt");
      Charset charset = Charset.forName("ISO-8859-1");
      try {
         List<String> lines = Files.readAllLines(wiki_path, charset);
         for (String line : lines) {
            System.out.println(line);
         }
      } 
      catch (IOException e) {
         System.out.println(e);
      }
   }	
}

輸出

To be or not to be?
  • createDirectories(Path dir, FileAttribute<?>...attrs) − 此方法用於透過建立所有不存在的父目錄來使用給定路徑建立目錄。

  • delete(Path path) − 此方法用於從指定路徑刪除檔案。如果檔案在指定路徑不存在,或者如果檔案是目錄且可能不為空且無法刪除,則會丟擲 NoSuchFileException。

  • exists(Path path) − 此方法用於檢查檔案是否存在於指定路徑,如果檔案存在,則返回 true,否則返回 false。

  • readAllBytes(Path path) − 此方法用於讀取給定路徑下檔案的所有位元組,並返回包含從檔案讀取的位元組的位元組陣列。

示例

package com.java.nio;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class ReadFile {
   public static void main(String[] args) {
      Path wiki_path = Paths.get("D:file.txt");
      Charset charset = Charset.forName("ISO-8859-1");
      try {
         List<String> lines = Files.readAllLines(wiki_path, charset);
         for (String line : lines) {
            System.out.println(line);
         }
      } 
      catch (IOException e) {
         System.out.println(e);
      }
   }	
}

輸出

Welcome to file.
  • size(Path path) − 此方法用於以位元組為單位獲取指定路徑下檔案的大小。

  • write(Path path, byte[] bytes, OpenOption… options) − 此方法用於將位元組寫入指定路徑下的檔案。

示例

package com.java.nio;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class WriteFile {
   public static void main(String[] args) {
      Path path = Paths.get("D:file.txt");
      String question = "To be or not to be?";
      Charset charset = Charset.forName("ISO-8859-1");
      try {
         Files.write(path, question.getBytes());
         List<String> lines = Files.readAllLines(path, charset);
         for (String line : lines) {
            System.out.println(line);
         }
      } 
      catch (IOException e) {
         System.out.println(e);
      }
   }
}

輸出

To be or not to be?

Java NIO - 非同步檔案通道

眾所周知,Java NIO 支援併發和多執行緒,這使我們能夠同時併發處理不同的通道。因此,Java NIO 包中負責此功能的 API 是 AsynchronousFileChannel,它定義在 NIO 通道包下。因此,AsynchronousFileChannel 的限定名稱為 java.nio.channels.AsynchronousFileChannel

AsynchronousFileChannel 類似於 NIO 的 FileChannel,只是此通道允許檔案操作非同步執行,這與同步 I/O 操作不同,在同步 I/O 操作中,執行緒進入操作並等待請求完成。因此,非同步通道可以安全地被多個併發執行緒使用。

在非同步中,執行緒將請求傳遞給作業系統的核心以完成它,而執行緒繼續處理另一個作業。核心的作業完成後,它會向執行緒發出訊號,然後執行緒確認訊號並中斷當前作業,並根據需要處理 I/O 作業。

為了實現併發,此通道提供了兩種方法,一種是返回 java.util.concurrent.Future 物件,另一種是將型別為 java.nio.channels.CompletionHandler 的物件傳遞給操作。

我們將透過示例逐一瞭解這兩種方法。

  • Future 物件 − 在此,從通道返回 Future 介面的一個例項。在 Future 介面中,有一個 get() 方法,它返回非同步處理的操作狀態,在此基礎上可以決定其他任務的進一步執行。我們還可以透過呼叫其 isDone 方法來檢查任務是否已完成。

示例

以下示例演示瞭如何使用 Future 物件並非同步執行任務。

package com.java.nio;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class FutureObject {
   public static void main(String[] args) throws Exception {
      readFile();
   }
   private static void readFile() throws IOException, InterruptedException, ExecutionException {
      String filePath = "D:fileCopy.txt";
      printFileContents(filePath);
      Path path = Paths.get(filePath);		
      AsynchronousFileChannel channel =AsynchronousFileChannel.open(path, StandardOpenOption.READ);
      ByteBuffer buffer = ByteBuffer.allocate(400);
      Future<Integer> result = channel.read(buffer, 0); // position = 0
      while (! result.isDone()) {
         System.out.println("Task of reading file is in progress asynchronously.");
      }
      System.out.println("Reading done: " + result.isDone());
      System.out.println("Bytes read from file: " + result.get()); 
      buffer.flip();
      System.out.print("Buffer contents: ");
      while (buffer.hasRemaining()) {
         System.out.print((char) buffer.get());                
      }
      System.out.println(" ");
      buffer.clear();
      channel.close();
   }
   private static void printFileContents(String path) throws IOException {
      FileReader fr = new FileReader(path);
      BufferedReader br = new BufferedReader(fr);
      String textRead = br.readLine();
      System.out.println("File contents: ");
      while (textRead != null) {
         System.out.println("     " + textRead);
         textRead = br.readLine();
      }
   fr.close();
   br.close();
   }
}

輸出

File contents: 
   To be or not to be?
   Task of reading file is in progress asynchronously.
   Task of reading file is in progress asynchronously.
   Reading done: true
   Bytes read from file: 19
   Buffer contents: To be or not to be? 
  • Completion Handler

    此方法非常簡單,因為它使用 CompletionHandler 介面並覆蓋其兩個方法,一個是 completed() 方法,當 I/O 操作成功完成時呼叫;另一個是 failed() 方法,如果 I/O 操作失敗則呼叫。在此,建立了一個處理程式來使用非同步 I/O 操作的結果,因為只有在任務完成後,處理程式才具有執行的功能。

示例

以下示例演示瞭如何使用 CompletionHandler 非同步執行任務。

package com.java.nio;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class CompletionHandlerDemo {
   public static void main (String [] args) throws Exception {
      writeFile();
   }
   private static void writeFile() throws IOException {
      String input = "Content to be written to the file.";
      System.out.println("Input string: " + input);
      byte [] byteArray = input.getBytes();
      ByteBuffer buffer = ByteBuffer.wrap(byteArray);
      Path path = Paths.get("D:fileCopy.txt");
      AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);
      CompletionHandler handler = new CompletionHandler() {
         @Override
         public void completed(Object result, Object attachment) {
            System.out.println(attachment + " completed and " + result + " bytes are written.");
         }
         @Override
         public void failed(Throwable exc, Object attachment) {
            System.out.println(attachment + " failed with exception:");
            exc.printStackTrace();
         }
      };
      channel.write(buffer, 0, "Async Task", handler);
      channel.close();
      printFileContents(path.toString());
   }
   private static void printFileContents(String path) throws IOException {
      FileReader fr = new FileReader(path);
      BufferedReader br = new BufferedReader(fr);
      String textRead = br.readLine();
      System.out.println("File contents: ");
      while (textRead != null) {
         System.out.println("     " + textRead);
         textRead = br.readLine();
      }
      fr.close();
      br.close();
   }
}

輸出

Input string: Content to be written to the file.
Async Task completed and 34 bytes are written.
File contents: 
Content to be written to the file.

Java NIO - 字元集

在 Java 中,每個字元都有一個定義良好的 Unicode 程式碼單元,它由 JVM 內部處理。因此,Java NIO 包定義了一個名為 Charset 的抽象類,該類主要用於字元集和 UNICODE 的編碼和解碼。

標準字元集

Java 中支援的字元集如下所示。

  • US-ASCII − 七位 ASCII 字元。

  • ISO-8859-1 − ISO 拉丁字母。

  • UTF-8 − 這是 8 位 UCS 轉換格式。

  • UTF-16BE − 這是 16 位 UCS 轉換格式,採用大端位元組序。

  • UTF-16LE − 這是 16 位 UCS 轉換格式,採用小端位元組序。

  • UTF-16 − 16 位 UCS 轉換格式。

Charset 類的重要方法

  • forName() − 此方法為給定的字元集名稱建立一個字元集物件。名稱可以是規範名稱或別名。

  • displayName() − 此方法返回給定字元集的規範名稱。

  • canEncode() − 此方法檢查給定字元集是否支援編碼。

  • decode() − 此方法將給定字元集的字串解碼為 Unicode 字元集的字元緩衝區。

  • encode() − 此方法將 Unicode 字元集的字元緩衝區編碼為給定字元集的位元組緩衝區。

示例

以下示例說明了 Charset 類的重要方法。

package com.java.nio;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
public class CharsetExample {
   public static void main(String[] args) {
      Charset charset = Charset.forName("US-ASCII");
      System.out.println(charset.displayName());
      System.out.println(charset.canEncode());
      String str = "Demo text for conversion.";
      //convert byte buffer in given charset to char buffer in unicode
      ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
      CharBuffer charBuffer = charset.decode(byteBuffer);
      //convert char buffer in unicode to byte buffer in given charset
      ByteBuffer newByteBuffer = charset.encode(charBuffer);
      while(newbb.hasRemaining()){
         char ch = (char) newByteBuffer.get();
         System.out.print(ch);
      }
      newByteBuffer.clear();
   }
}

輸出

US-ASCII
Demo text for conversion.

Java NIO - 檔案鎖

眾所周知,Java NIO 支援併發和多執行緒,這使得它能夠處理多個執行緒同時操作多個檔案。但在某些情況下,我們需要確保我們的檔案不會被任何執行緒共享並變得不可訪問。

對於此類需求,NIO 再次提供了一個名為 FileLock 的 API,用於在整個檔案或檔案的一部分上提供鎖定,以確保檔案或其部分不會被共享或訪問。

為了提供或應用此類鎖定,我們必須使用 FileChannel 或 AsynchronousFileChannel,它們為此目的提供了兩種方法 lock()tryLock()。提供的鎖定可以是兩種型別之一 −

  • 獨佔鎖定 − 獨佔鎖定阻止其他程式獲取任何型別的重疊鎖定。

  • 共享鎖定 − 共享鎖定阻止其他併發執行的程式獲取重疊的獨佔鎖定,但允許它們獲取重疊的共享鎖定。

用於獲取檔案鎖定的方法 −

  • lock() − FileChannel 或 AsynchronousFileChannel 的此方法獲取與給定通道關聯的檔案上的獨佔鎖定。此方法的返回型別是 FileLock,它進一步用於監控獲得的鎖定。

  • lock(long position, long size, boolean shared) − 此方法又是 lock 方法的過載方法,用於鎖定檔案的特定部分。

  • tryLock() − 此方法返回一個 FileLock 或 null(如果無法獲取鎖定),它嘗試在此通道的檔案上獲取一個顯式的獨佔鎖定。

  • tryLock(long position, long size, boolean shared) − 此方法嘗試獲取此通道檔案給定區域上的鎖定,該鎖定可以是獨佔型別或共享型別。

FileLock 類的使用方法

  • acquiredBy() − 此方法返回獲取檔案鎖定的通道。

  • position() − 此方法返回鎖定區域第一個位元組在檔案中的位置。鎖定區域不必包含在實際的基礎檔案中,甚至不必與之重疊,因此此方法返回的值可能超過檔案的當前大小。

  • size() − 此方法返回鎖定區域的大小(以位元組為單位)。鎖定區域不必包含在實際的基礎檔案中,甚至不必與之重疊,因此此方法返回的值可能超過檔案的當前大小。

  • isShared() − 此方法用於確定鎖定是否為共享鎖定。

  • overlaps(long position,long size) − 此方法指示此鎖定是否與給定的鎖定範圍重疊。

  • isValid() − 此方法指示獲得的鎖定是否有效。鎖定物件保持有效,直到它被釋放或關聯的檔案通道被關閉,以先發生者為準。

  • release() − 釋放獲得的鎖定。如果鎖定物件有效,則呼叫此方法會釋放鎖定並使物件無效。如果此鎖定物件無效,則呼叫此方法沒有任何效果。

  • close() − 此方法呼叫 release() 方法。它被新增到類中,以便可以與自動資源管理塊構造一起使用。

演示檔案鎖定的示例。

以下示例在檔案上建立鎖定並向其寫入內容

package com.java.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FileLockExample {
   public static void main(String[] args) throws IOException {
      String input = "Demo text to be written in locked mode.";  
      System.out.println("Input string to the test file is: " + input);  
      ByteBuffer buf = ByteBuffer.wrap(input.getBytes());  
      String fp = "D:file.txt";  
      Path pt = Paths.get(fp);  
      FileChannel channel = FileChannel.open(pt, StandardOpenOption.WRITE,StandardOpenOption.APPEND);  
      channel.position(channel.size() - 1); // position of a cursor at the end of file       
      FileLock lock = channel.lock();   
      System.out.println("The Lock is shared: " + lock.isShared());  
      channel.write(buf);  
      channel.close(); // Releases the Lock  
      System.out.println("Content Writing is complete. Therefore close the channel and release the lock.");  
      PrintFileCreated.print(fp);  
   }  
}

package com.java.nio;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class PrintFileCreated {
   public static void print(String path) throws IOException {  
      FileReader filereader = new FileReader(path);  
      BufferedReader bufferedreader = new BufferedReader(filereader);  
      String tr = bufferedreader.readLine();    
      System.out.println("The Content of testout.txt file is: ");  
      while (tr != null) {      
         System.out.println("    " + tr);  
         tr = bufferedreader.readLine();  
      }  
   filereader.close();  
   bufferedreader.close();  
   }  
}

輸出

Input string to the test file is: Demo text to be written in locked mode.
The Lock is shared: false
Content Writing is complete. Therefore close the channel and release the lock.
The Content of testout.txt file is: 
To be or not to be?Demo text to be written in locked mode.
廣告