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.
广告