Java NIO - 快速指南

Java NIO - 概述

Java.nio 包是在 java 1.4 中引入的。与 Java I/O 相比,Java NIO 引入了面向缓冲区和通道的 I/O 操作数据流,从而提供更快的执行速度和更好的性能。

此外,NIO API 还提供选择器,它引入了以异步或非阻塞方式监听多个通道的 IO 事件的功能。在 NIO 中,最耗时的 I/O 活动包括向操作系统填充和清空缓冲区,从而提高速度。

NIO API 的核心抽象如下 −

  • 缓冲区是数据、字符集及其相关解码器和编码器的容器,可在字节和 Unicode 字符之间进行转换。

  • 各种类型的通道,表示与能够执行 I/O 操作的实体的连接

  • 选择器和选择键,它们与可选通道一起定义了多路复用、非阻塞 I/O设施。

Java NIO - 环境设置

本部分将指导您如何在您的机器上下载和设置 Java。请按照以下步骤设置环境。

Java SE 可从链接下载 Java免费获得。因此,您可以根据您的操作系统下载一个版本。

按照说明下载 Java 并运行 .exe 以在您的机器上安装 Java。在计算机上安装 Java 后,您需要设置环境变量以指向正确的安装目录 −

设置 Windows 2000/XP 的路径

假设您已在 c:\Program Files\java\jdk 目录中安装了 Java −

  • 右键单击"我的电脑",然后选择"属性"。

  • 单击"高级"选项卡下的"环境变量"按钮。

  • 现在更改"Path"变量,使其也包含 Java 可执行文件的路径。例如,如果路径当前设置为"C:\WINDOWS\SYSTEM32",则将路径更改为"C:\WINDOWS\SYSTEM32;c:\Program Files\java\jdk\bin"。

设置适用于 Windows 95/98/ME 的路径

假设您已在 c:\Program Files\java\jdk 目录 −

中安装了 Java
  • 编辑"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 还支持多线程,以便可以异步读取和写入数据,这样在执行 IO 操作时当前线程就不会被阻塞。这再次使其比传统的 Java IO API 更高效。

  • 随着 Java NIO 中选择器的引入,引入了多线程的概念,它允许以异步或非阻塞的方式监听多个通道的 IO 事件方式。

  • NIO 中的多线程使其非阻塞,这意味着只有在数据可用时才请求线程读取或写入,否则线程可以同时用于其他任务。但在传统 Java IO 的情况下这是不可能的,因为它不支持多线程,这使其成为阻塞。

  • NIO 允许仅使用单个线程管理多个通道,但代价是解析数据可能比在 Java IO 的情况下从阻塞流读取数据时要复杂一些。因此,如果需要更少的连接和非常高的带宽,并且一次发送大量数据,那么在这种情况下,Java IO API 可能是最合适的。

Java NIO - 通道

描述

顾名思义,通道用作从一端到另一端的数据流的平均值。在 Java NIO 中,通道在缓冲区和另一端的实体之间起着相同的作用换句话说,通道用于将数据读取到缓冲区,也可以从缓冲区写入数据。

与传统 Java IO 中使用的流不同,通道是双向的,即可以读取也可以写入。Java NIO 通道支持阻塞和非阻塞模式下的异步数据流。

通道的实现

Java NIO 通道主要在以下类中实现 −

  • FileChannel − 为了从文件中读取数据,我们使用文件通道。只能通过调用文件对象上的 getChannel() 方法来创建文件通道的对象,因为我们无法直接创建文件对象。

  • DatagramChannel −数据报通道可以通过 UDP(用户数据报协议)在网络上读取和写入数据。可以使用工厂方法创建 DataGramchannel 对象。

  • SocketChannel − SocketChannel 通道可以通过 TCP(传输控制协议)在网络上读取和写入数据。它还使用工厂方法来创建新对象。

  • ServerSocketChannel − 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 {
      //将内容附加到现有文件
      writeFileChannel(ByteBuffer.wrap("Welcome to TutorialsPoint".getBytes()));
      //读取文件
      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() 方法。但是,数据报通道不需要连接才能使用发送和接收方法,而必须连接才能使用读取和写入方法,因为这些方法不接受或返回套接字地址。

我们可以通过调用其 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);
      //从客户端接收缓冲区。
      SocketAddress remoteAdd = server.receive(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();
   }
}

输出

运行客户端不会打印任何内容,直到服务器启动。


Server: 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 NIO 服务器套接字通道又是一种可选类型的通道,用于连接套接字的面向流的数据流。可以通过调用其静态 open() 方法来创建服务器套接字通道,前提是任何预先存在的套接字都不存在。服务器套接字通道是通过调用 open 方法创建的,但尚未绑定。要绑定套接字通道,必须调用 bind() 方法。

这里要提到的一点是,如果通道未绑定,并且尝试执行任何 I/O 操作,则此通道会抛出 NotYetBoundException。因此,在执行任何 IO 操作之前,必须确保通道已绑定。

通过调用 ServerSocketChannel.accept() 方法来监听服务器套接字通道的传入连接。当 accept() 方法返回时,它会返回一个带有传入连接的 SocketChannel。因此,accept() 方法会阻塞,直到传入连接到达。如果通道处于非阻塞模式,则如果没有待处理的连接,accept 方法将立即返回 null。否则,它将无限期阻塞,直到有新的连接可用或发生 I/O 错误。

新通道的套接字最初未绑定;必须通过其套接字的绑定方法之一将其绑定到特定地址,然后才能接受连接。此外,新通道是通过调用系统范围默认 SelectorProvider 对象的 openServerSocketChannel 方法创建的。

与套接字通道一样,服务器套接字通道可以使用 read() 方法读取数据。首先分配缓冲区。从 ServerSocketChannel 读取的数据存储在缓冲区中。其次,我们调用 ServerSocketChannel.read() 方法,它将数据从 ServerSocketChannel 读入缓冲区。 read() 方法的整数值返回写入缓冲区的字节数

类似地,可以使用 write() 方法将数据写入服务器套接字通道,并使用缓冲区作为参数。通常在 while 循环中使用 write 方法,因为需要重复 write() 方法,直到缓冲区没有其他可写入的字节。

套接字通道的重要方法

  • bind(SocketAddress local) − 此方法用于将套接字通道绑定到作为此方法的参数提供的本地地址。

  • accept() − 此方法用于接受与此通道套接字的连接。

  • connect(SocketAddress remote) −此方法用于将套接字连接到远程地址。

  • finishConnect() − 此方法用于完成连接套接字通道的过程。

  • getRemoteAddress() − 此方法返回通道套接字所连接的远程位置的地址。

  • isConnected() − 如前所述,此方法返回套接字通道的连接状态,即是否已连接。

  • open() − Open 方法用于打开未指定地址的套接字通道。此便捷方法的工作方式类似于调用 open() 方法,在生成的服务器套接字通道上调用 connect 方法,将其传递给远程,然后返回该通道。

  • 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();
   }
}

输出

运行客户端不会打印任何内容,直到服务器启动。


Server: 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 提供的另一个额外支持是从多个缓冲区到通道读取/写入数据。这种多次读写支持称为分散和聚集,其中,在读取数据的情况下,数据从单个通道分散到多个缓冲区,而在写入数据的情况下,数据从多个缓冲区收集到单个通道。

为了实现从通道的多次读写,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 中的分散/聚集方法是一种优化和多任务的方法。它允许您将繁重的工作委托给操作系统,将您读取的数据分离到多个存储桶中,或将不同的数据块组装成一个整体。毫无疑问,这可以节省时间,通过避免缓冲区复制来更有效地使用操作系统,并减少需要编写和调试的代码量。

Java NIO - 聚集

众所周知,与 Java 的传统 IO API 相比,Java NIO 是一种针对数据 IO 操作更优化的 API。Java NIO 提供的另一个额外支持是从多个缓冲区读取/写入数据到通道。这种多次读写支持称为分散和聚集,其中在读取数据的情况下,数据从单个通道分散到多个缓冲区,而在写入数据的情况下,数据从多个缓冲区收集到单个通道。

为了实现这种多次读写,通道有 ScatteringByteChannel 和 GatheringByteChannel API,Java NIO 提供这两个 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);
      // 接下来的两个缓冲区保存着我们要写入的数据
      ByteBuffer bstream1 = ByteBuffer.wrap(stream1.getBytes());
      ByteBuffer bstream2 = ByteBuffer.wrap(stream2.getBytes());
      int len1 = stream1.length();
      int len2 = stream2.length();
      // 将长度(数据)写入缓冲区
      bLen1.asIntBuffer().put(len1);
      bLen2.asIntBuffer().put(len2);
      System.out.println("Gathering : Len1 = " + len1);
      System.out.println("Gathering : Len2 = " + len2);
      // 将数据写入文件
      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 中的分散/聚集方法是一种优化和多任务的方法。它允许您将繁重的工作委托给操作系统,将您读取的数据分离到多个存储桶中,或将不同的数据块组装成一个整体。毫无疑问,这可以节省时间,通过避免缓冲区复制来更有效地使用操作系统,并减少需要编写和调试的代码量。

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

Buffer 的重要方法

如前所述,Buffer 充当内存对象,提供一组方法,使处理内存块更加方便。以下是 Buffer 的重要方法 −

  • allocate(int capacity) − 此方法用于分配一个以容量为参数的新缓冲区。如果传递的容量为负整数,Allocate 方法将抛出 IllegalArgumentException。

  • read() 和 put() − 通道的 read 方法用于将数据从通道写入缓冲区,而缓冲区的 put 方法用于将数据写入缓冲区。

  • flip() − flip 方法将缓冲区的模式从写入模式切换到读取模式。它还将位置设置回 0,并将限制设置为写入时位置的位置。

  • write() 和 get() −通道的 write 方法用于将数据从缓冲区写入通道,而 get 是缓冲区的方法,用于从缓冲区读取数据。

  • rewind() − 当需要重新读取时使用 rewind 方法,因为它将位置设置回零并且不会改变 limit 的值。

  • clear() 和 compact() − clear 和 compact 两种方法均用于将缓冲区从读取模式转换为写入模式。clear() 方法将位置设置为零,并将限制设置为容量,在此方法中,缓冲区中的数据不会被清除,只有标记会被重新初始化。

    另一方面,当仍有一些未读取的数据且我们仍然使用缓冲区的写入模式时,使用 compact() 方法,在这种情况下, compact 方法将所有未读数据复制到缓冲区的开头,并将位置设置为最后一个未读元素之后。限制属性仍设置为容量。

  • mark() 和 reset() − 顾名思义,mark 方法用于标记缓冲区中的任何特定位置,而 reset 使位置回到标记位置。

示例

以下示例显示了上述定义方法的实现。

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

public class BufferDemo {
   public static void main (String [] args) {
    //分配一个字符类型缓冲区。
    CharBuffer buffer = CharBuffer.allocate(10);
    String text = "bufferDemo";
    System.out.println("输入文本:" + text);
    for (int i = 0; i < text.length(); i++) {
        char c = text.charAt(i);
        //将字符放入缓冲区。
        buffer.put(c);
    }
    int buffPos = buffer.position();
    System.out.println("数据写入缓冲区后的位置:" + buffPos);
    buffer.flip();
    System.out.println("正在读取缓冲区内容:");
    while (buffer.hasRemaining()) {
    System.out.println(buffer.get());
    }
    //将缓冲区的位置设置为 5。
    buffer.position(5);
    //将此缓冲区的标记设置为其位置
    buffer.mark();
    //尝试改变位置
    buffer.position(6);
    //调用 reset 方法恢复到我们标记的位置。
    //如果新位置小于
    //标记的位置或 merk 尚未设置,则 reset() 引发 InvalidMarkException。
    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 提供选择器。

使用选择器,我们可以创建一个线程来了解哪个通道已准备好进行数据写入和读取,并可以处理该特定通道。

我们可以通过调用其静态方法 open() 来获取选择器实例。打开选择器后,我们必须向其注册一个非阻塞模式通道,该通道返回 SelectionKey 的实例。

SelectionKey 基本上是可以使用通道执行的操作的集合,或者我们可以说,我们可以借助选择键了解通道的状态。

选择键代表的主要操作或通道状态是 −

  • SelectionKey.OP_CONNECT − 准备连接服务器的通道。

  • SelectionKey.OP_ACCEPT − 准备接受传入连接的通道。

  • SelectionKey.OP_READ − 准备读取数据的通道。

  • SelectionKey.OP_WRITE − 准备写入数据的通道。

注册后获得的Selection Key有一些重要的方法,如下所述 −

  • 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 中,管道被定义为一个抽象类,主要有三种方法,其中两种是抽象的。

管道类的方法

  • 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 {
    //创建 Pipe 的一个实例
    Pipe pipe = Pipe.open();
    // 获取管道的接收通道
    Pipe.SinkChannel skChannel = pipe.sink();
    String testData = "测试数据以检查 java NIO 通道管道。";
    ByteBuffer buffer = ByteBuffer.allocate(512);
    buffer.clear();
    buffer.put(testData.getBytes());
    buffer.flip();
    //将数据写入接收通道。
    while(buffer.hasRemaining()) {
    skChannel.write(buffer);
    }
    //获取管道的源通道
    Pipe.SourceChannel sourceChannel = pipe.source();
    buffer = ByteBuffer.allocate(512);
    //将数据写入控制台
    while(sourceChannel.read(buffer) > 0){
        //limit 设置为当前位置,position 设置为零
        buffer.flip();
        while(buffer.hasRemaining()){
            char ch = (char) buffer.get();
            System.out.print(ch);
        }
        //position 设置为零,limit 设置为 capacity,以清除缓冲区。
        buffer.clear();
    }
   }
}

输出

Test Data to Check java NIO Channels Pipe.

假设我们有一个文本文件c:/test.txt,其中包含以下内容。该文件将用作示例程序的输入。

Java NIO - 路径

顾名思义,路径是文件系统中文件或目录等实体的特定位置,以便人们可以在该特定位置搜索和访问它。

从 Java 技术上讲,路径是 Java 版本 7 期间在 Java NIO 文件包中引入的一个接口,是特定文件系统中位置的表示。由于路径接口位于 Java NIO 包中,因此它的限定名称为 java.nio.file.Path。

一般来说,实体的路径可以分为两种类型,一种是绝对路径,另一种是相对路径。正如这两种路径的名称所暗示的那样,绝对路径是从根到它所在的实体的位置地址,而相对路径是相对于其他路径的位置地址。路径在其定义中使用分隔符,对于 Windows 使用"\",对于 unix 操作系统使用"/"。

为了获取路径的实例,我们可以使用 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() − 返回此路径的名称元素子序列的相对 Path。

  • 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 包中的。因此本教程适用于相同的文件包。

此类仅由对文件、目录或其他类型文件进行操作的静态方法组成。在大多数情况下,此处定义的方法将委托给关联的文件系统提供程序来执行文件操作。

Files 类中定义了许多方法,也可以从 Java 文档中读取。在本教程中,我们尝试介绍 Java NIO Files 类的所有方法中的一些重要方法。

Files 类的重要方法。

以下是 Java NIO Files 类中定义的重要方法。

  • createFile(Path filePath, FileAttribute attrs) − Files 类提供此方法使用指定路径创建文件。

示例

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) {
      //初始化路径对象
      Path path = Paths.get("D:file.txt");
      //创建文件
      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 - AsynchronousFileChannel

众所周知,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? 
  • 完成处理程序

    这种方法非常简单,因为在此我们使用 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 - CharSet

在 Java 中,每个字符都有一个定义明确的 unicode 代码单元,由 JVM 内部处理。因此,Java NIO 包定义了一个名为 Charset 的抽象类,主要用于对字符集和 UNICODE 进行编码和解码。

标准字符集

Java 中支持的 Charset 如下。

  • 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.";
      //将给定字符集中的字节缓冲区转换为unicode中的字符缓冲区
      ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
      CharBuffer charBuffer = charset.decode(byteBuffer);
      //将unicode中的字符缓冲区转换为给定字符集中的字节缓冲区
      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 - FileLock

众所周知,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); // 光标在文件末尾的位置
      FileLock lock = channel.lock();   
      System.out.println("The Lock is shared: " + lock.isShared());  
      channel.write(buf);  
      channel.close(); // 释放锁
      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.