Java Nio 简明教程

Java NIO - Quick Guide

Java NIO - Overview

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

NIO API 还提供了选择器,它引入了以异步或非阻塞方式侦听多个通道的 I/O 事件的功能。在 NIO 中,包括将缓冲区填充和清空到操作系统等最耗时的 I/O 活动在内的速度都得到了提升。

NIO API 的核心抽象如下:

  1. 缓冲区(用于盛放数据)、字符集及其关联的解码器和编码器(用于在字节和 Unicode 字符之间进行转换)。

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

  3. 选择器和选择键,它们与可选择的通道共同定义一个多路复用、非阻塞 I/O 设施。

Java NIO - Environment Setup

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

Java SE 可以从链接 Download Java 中免费获取。因此,您可以根据自己的操作系统下载版本。

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

Setting up the path for windows 2000/XP

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

  1. 右键单击“我的电脑”,然后选择“属性”。

  2. 单击“高级”选项卡下的“环境变量”按钮。

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

Setting up the path for windows 95/98/ME

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

  1. 编辑“C:\autoexec.bat”文件并在末尾添加以下行:“SET PATH = %PATH%;C:\Program Files\java\jdk\bin”

Setting up the path for Linux, UNIX, Solaris, FreeBSD

环境变量 PATH 应设置为指向已安装 Java 二进制文件的位置。如果您在执行此操作时遇到问题,请参阅您的 shell 文档。

例如,如果您使用 bash 作为您的 shell,那么您将向 '.bashrc 的尾部添加以下行: export PATH = /path/to/java:$PATH'

要编写 Java 程序,您需要一个文本编辑器。市场上还有更多高级的 IDE 可用。但现在,您可以考虑以下选项之一 −

  1. Notepad − 在 Windows 计算机上,您可以使用任何简单的文本编辑器,例如记事本(推荐用于本教程)、TextPad。

  2. Netbeans − 这是一个开源且免费的 Java IDE,可以从 http://www.netbeans.org/index.html 下载。

  3. Eclipse − 它也是由 Eclipse 开源社区开发的一个 Java IDE,可以从 https://www.eclipse.org/ 下载。

Java NIO vs IO

正如我们所知,Java NIO 引入是为了提升常规 Java IO API。NIO 比 IO 更有效率的主要改进是 NIO 中使用的数据流模型,以及使用操作系统执行常规 IO 任务。

Java NIO 和 Java IO 之间的差异可以解释如下 −

  1. 如前一篇帖子中所述,NIO 中面向缓冲区和面向管道的 I/O 操作数据流,与 IO 相比,提供了更快的执行速度和更好的性能。同时,NIO 使用操作系统执行常规 I/O 任务,再次使其效率更高。

  2. NIO 和 IO 之间的另一个差异是:IO 使用流线数据流,即一次一个字节,并且依赖于将数据对象转换为字节,反之亦然;而 NIO 则处理由字节块组成的块数据。

  3. 在 Java IO 中,流对象是单向的,而在 NIO 中,管道是双向的,这意味着管道可以用于读写数据。

  4. IO 中的流线数据流不允许前后移动数据。如果需要在数据中前后移动,需要先将从流中读取的数据缓存到缓冲区中。而在 NIO 中,我们使用面向缓冲区的访问方式,它允许在无需缓存的情况下前后访问数据。

  5. NIO API 也支持多线程,以便可以异步地读写数据,这样在执行 IO 操作时,当前线程不会被阻塞。这使得它比传统 Java IO API 更高效。

  6. 多线程的概念是随 Selectors 引入的,它允许以异步或非阻塞方式监听多个 IO 事件通道。

  7. NIO 中的多线程使其变成非阻塞,这意味着只有在数据可用时才请求线程读写,否则可以将线程用于其他任务。然而,在传统 Java IO 中这是不可能的,因为它不支持多线程,使其成为阻塞方式。

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

Java NIO - Channels

Description

顾名思义,管道用作从一端到另一端的数据流方式。此处在 Java NIO 中,管道在缓冲区和另一端的一个实体之间起着同样的作用,换句话说,管道用于将数据读入缓冲区并从缓冲区写入数据。

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

Implementations of Channel

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

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

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

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

  4. ServerSocketChannel − ServerSocketChannel 读取和写入 TCP 连接上的数据,就像 Web 服务器一样。对于每个传入连接,都会创建一个 SocketChannel。

Example

以下示例从 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();
   }
}

Output

Hello World!

Java NIO - File Channel

Description

如前所述,Java NIO 通道的 FileChannel 实现被引入以访问文件的元数据属性,包括创建、修改、大小等。除此之外,文件通道是多线程的,这再次使 Java NIO 比 Java IO 更有效。

一般来说,我们可以说 FileChannel 是连接到文件的通道,通过它你可以从文件读取数据,并向文件写入数据。FileChannel 的另一个重要特征是它不能被设置为非阻塞模式,并且始终以阻塞模式运行。

我们无法直接获取文件通道对象,文件通道对象可以通过以下方法获得:

  1. getChannel() - FileInputStream、FileOutputStream 或 RandomAccessFile 上的方法。

  2. open() - File 通道的方法,默认打开通道。

文件通道的对象类型取决于调用对象创建时所调用的类类型,即如果对象是通过调用 FileInputStream 的 getchannel 方法创建的,则文件通道将被打开以进行读取,并且在尝试写入时将抛出 NonWritableChannelException。

Example

以下示例显示如何从 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();
   }
}

Output

Hello World! Welcome to TutorialsPoint

Java NIO - Datagram Channel

Java NIO Datagram 用作在无连接协议上发送和接收 UDP 数据包的通道。默认情况下,数据报通道在阻塞,虽然它可以在非阻塞模式下使用。为了使它非阻塞,我们可以使用 configureBlocking(false) 方法。数据报通道可以通过调用其名为 open() 的静态方法之一来打开,该方法还可以使用 IP 地址作为参数,以便可以用于多播。

数据报通道类似于 FileChannel,默认情况下不进行连接,因此为了实现连接,我们必须显式调用其 connect() 方法。然而,数据报通道不需要连接才能使用 send 和 receive 方法,而它必须连接才能使用 read 和 write 方法,因为那些方法不接受或不返回套接字地址。

我们可以通过调用其 isConnected() 方法来检查数据报通道的连接状态。一旦连接,数据报通道将一直保持连接状态,直到它断开或关闭。数据报通道是线程安全的,并且同时支持多线程和并发。

Important methods of datagram channel

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

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

  3. disconnect() − 此方法用于断开与远程地址的套接字连接。

  4. getRemoteAddress() - 该方法返回通道 Socket 连接到的远程位置的地址。

  5. isConnected() − 如前所述,此方法返回数据报通道连接状态,即是否已连接。

  6. open() and open(ProtocolFamily family) − open 方法用于为单个地址打开数据报通道,而参数化 open 方法为用作协议族表示的多个地址打开通道。

  7. read(ByteBuffer dst) − 此方法用于通过数据报通道从给定的缓冲区读取数据。

  8. receive(ByteBuffer dst) − 此方法用于通过此通道接收数据报。

  9. send(ByteBuffer src, SocketAddress target) − 此方法用于通过此通道发送数据报。

Example

以下示例显示如何通过 Java NIO DataGramChannel 发送数据。

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

Output

Server Started: localhost/127.0.0.1:8989

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

Output

运行客户端将在服务器上打印以下输出。

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

Java NIO - Socket Channel

Java NIO 套接字通道是一种可选择类型通道,这意味着它可以使用选择器多路复用,用于面向流的数据流连接套接字。套接字通道可以通过调用其静态 open() 方法创建,前提是没有任何预先存在的套接字。套接字通道是通过调用 open 方法创建的,但尚未连接。为了连接套接字通道,需要调用 connect() 方法。这里需要注意的一点是,如果通道未连接并且试图尝试任何 I/O 操作,则此通道将抛出 NotYetConnectedException。因此,必须确保在执行任何 IO 操作之前通道已连接。一旦通道连接上,它将保持连接状态,直到它被关闭。可以通过调用其 isConnected 方法来确定套接字通道的状态。

套接字通道的连接可以通过调用其 finishConnect() 方法完成。是否正在进行连接操作可以通过调用 isConnectionPending 方法来确定。默认情况下,套接字通道支持非阻塞连接。它还支持异步关闭,这类似于通道类中指定的异步关闭操作。

套接字通道可以安全地供多个并发线程使用。它们支持并发读和写,尽管最多只有一个线程可以读取,并且最多只有一个线程可以在任何给定时间写入。connect 和 finishConnect 方法彼此互斥同步,并且在其中一个方法的调用正在进行时尝试启动读取或写入操作将阻塞,直到该调用完成。

Important methods of Socket channel

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

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

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

  4. getRemoteAddress() - 该方法返回通道 Socket 连接到的远程位置的地址。

  5. isConnected() - 如前所述,该方法返回 Socket 通道的连接状态,即连接与否。

  6. open() and open((SocketAddress remote) - open 方法用于打开一个没有指定地址的 Socket 通道,而带参数的 open 方法用于打开一个具有指定远程地址的通道,并连接到它。该方便方法的工作方式如下:调用 open() 方法,调用结果 Socket 通道的 connect 方法,将 remote 传递给它,然后返回该通道。

  7. read(ByteBuffer dst) - 该方法用于通过 Socket 通道从给定的缓冲区中读取数据。

  8. isConnectionPending() - 该方法指示此通道上是否正在进行连接操作。

Example

以下示例说明如何从 Java NIO SocketChannel 发送数据。

C:/Test/temp.txt

Hello World!

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

Output

在服务器启动前运行客户端不会打印任何内容。

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

Output

运行服务器将打印以下内容。

Connection Set:  /127.0.0.1:49558
File Received

Java NIO - ServerSocket Channel

Java NIO 服务器套接字通道是再次用于流式数据流连接套接字的可选择类型通道。服务器套接字通道可通过唤起其静态` 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() 方法,直到缓冲区中没有可写入的可用字节为止。

Important methods of Socket channel

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

  2. ` accept() ` - 此方法用于接受对该通道的套接字建立的连接。

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

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

  5. getRemoteAddress() - 该方法返回通道 Socket 连接到的远程位置的地址。

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

  7. ` open() ` - 打开方法用于为未指定地址打开套接字通道。此简易方法的作用类似于通过唤起 open() 方法,唤起所得服务器套接字通道的连接方法,为其传递远程,然后再返回该通道。

  8. read(ByteBuffer dst) - 该方法用于通过 Socket 通道从给定的缓冲区中读取数据。

  9. ` setOption(SocketOption&lt;T&gt; name, T value) ` - 此方法设置套接字选项的值。

  10. ` socket() ` - 此方法检索与此通道关联的服务器套接字。

  11. ` validOps() ` - 此方法返回一个操作集,用于标识此通道支持的操作。服务器套接字通道仅支持接受新连接,因此此方法返回 SelectionKey.OP_ACCEPT。

Example

以下示例显示如何从 Java NIO ServerSocketChannel 发送数据。

C:/Test/temp.txt

Hello World!

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

Output

在服务器启动前运行客户端不会打印任何内容。

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

Output

运行服务器将打印以下内容。

Connection Set:  /127.0.0.1:49558
File Received

Java NIO - Scatter

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

为了实现从通道的多读多写,Java NIO 为读取和写入数据提供了 ScatteringByteChannel 和 GatheringByteChannel API,如以下示例所示。

ScatteringByteChannel

Read from multiple channels - 在此处,我们从单个通道中读取数据到多个缓冲区。为此,分配多个缓冲区并将其添加到缓冲区类型数组中。然后,将此数组作为参数传递给 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();
      }
   }
}

Output

Scattering : Len1 = 1214606444
Scattering : Len2 = 0

最后可以得出结论,如果正确使用,Java NIO 中的分散/聚集方法是一种经过优化且可以多任务处理。它允许你将分离出读入多个存储桶中的数据或将不同数据块组装成一个整体的繁琐工作委托给操作系统。毫无疑问,这节省了时间,并且通过避免缓冲区副本更有效地使用了操作系统,并减少了需要编写和调试的代码量。

Java NIO - Gather

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

为了实现从通道的多读多写,Java NIO 为读取和写入数据提供了 ScatteringByteChannel 和 GatheringByteChannel API,如以下示例所示。

GatheringByteChannel

write to multiple channels − 在此示例中,我们让多个缓冲区中的数据写入一个管道中。为此,再次分配了多个缓冲区,并将这些缓冲区添加到缓冲区类型数组中。然后将此数组作为参数传递给 GatheringByteChannel write() 方法,该方法依次从数组中的多个缓冲区写入数据。这里需要记住的一点是,只写入缓冲区的 position 和 limit 之间的数据。

以下示例显示了如何在 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();
      }
   }
}

Output

Gathering : Len1 = 24
Gathering : Len2 = 25

最后可以得出结论,如果正确使用,Java NIO 中的分散/聚集方法是一种经过优化且可以多任务处理。它允许你将分离出读入多个存储桶中的数据或将不同数据块组装成一个整体的繁琐工作委托给操作系统。毫无疑问,这节省了时间,并且通过避免缓冲区副本更有效地使用了操作系统,并减少了需要编写和调试的代码量。

Java NIO - Buffer

Java NIO 中的缓冲区可以看作一个简单的对象,它充当定大小数据块的容器,可用于向通道写入数据或从通道读取数据,以便缓冲区充当通道的端点。

它提供了成套的方法,以便于处理内存块以将数据读入通道或从通道写入数据。

与经典 IO 相比,缓冲区使得 NIO 包更有效率且更快速,因为在 IO 的情况下,数据是以流的形式处理的,不支持异步和并发的数据流。此外,IO 也不允许以块或字节组的形式执行数据。

定义 Java NIO 缓冲区的主要参数可以定义为:

  1. ` Capacity ` − 可以存储在缓冲区中的数据/字节的最大数量。不能更改缓冲区的容量。缓冲区已满后,在向其中写入数据之前应将其清除。

  2. Limit − 限制有意义,具体取决于缓冲区的模式,即在缓冲区的写入模式中,限制等于容量,这意味着可以在缓冲区中写入的最大数据量。而在缓冲区的读取模式中,限制意味着可以从缓冲区中读取的数据量。

  3. Position − 指向缓冲区中光标的当前位置。在创建缓冲区时最初设置为 0,或者换句话说,它是下一个要读取或写入元素的索引,它由 get() 和 put() 方法自动更新。

  4. Mark − 标记缓冲区中位置的书签。当调用 mark() 方法时,将记录当前位置,并在调用 reset() 时,将恢复已标记的位置。

Buffer Type

Java NIO 缓冲区可以根据缓冲区处理的数据类型进行以下分类:

  1. ByteBuffer

  2. MappedByteBuffer

  3. CharBuffer

  4. DoubleBuffer

  5. FloatBuffer

  6. IntBuffer

  7. LongBuffer

  8. ShortBuffer

Important methods of Buffer

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

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

  2. read() and put() − 通道的 read 方法用于将数据从通道写入缓冲区,而 put 是缓冲区的一种方法,用于将数据写入缓冲区。

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

  4. write() and get() − 通道的 write 方法用于将数据从缓冲区写入通道,而 get 是缓冲区的一种方法,用于从缓冲区读取数据。

  5. rewind() − 当需要重新读取时,使用 rewind 方法,因为它将位置设置回 0,并且不会更改限制的值。

  6. clear() and compact() − clear 和 compact 这两种方法都用于使缓冲区从读取模式变为写入模式。 clear() 方法将位置变为 0,并将限制变为容量,在这个方法中,缓冲区中的数据不会被清除,只有标记会被重新初始化。另一方面, compact() 方法在仍然有未读取的数据并且我们仍然使用缓冲区的写入模式的情况下使用,在这种情况下,compact 方法将所有未读取的数据复制到缓冲区的开头,并将位置设置到最后一个未读元素的后面。限制属性仍然设置为 capacity。

  7. mark() and reset() − 如名称所示,mark 方法用于标记缓冲区中的任何特定位置,而 reset 使位置回到标记的位置。

Example

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

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

Output

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 - Selector

如我们所知,Java NIO 支持从多个通道和缓冲区进行多个事务。因此,为了检查一个或多个 NIO 通道并确定哪些通道已准备好进行数据事务,即读取或写入,Java NIO 提供选择器。

使用选择器,我们可以让线程知道哪个通道已准备好进行数据写入和读取,并处理特定通道。

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

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

表示选择器密钥通道的主要操作或状态是:

  1. SelectionKey.OP_CONNECT - 已准备连接到服务器的通道。

  2. SelectionKey.OP_ACCEPT - 已准备好接受传入连接的通道。

  3. SelectionKey.OP_READ - 已准备好进行数据读取的通道。

  4. SelectionKey.OP_WRITE - 已准备好进行数据写入的通道。

注册后获得的选择器密钥具有以下几个重要方法:

  1. attach() - 此方法用于将对象附加到密钥。将对象附加到通道的主要目的是识别相同的通道。

  2. attachment() - 此方法用于从通道获取附加对象。

  3. channel() - 此方法用于获取为其创建特定密钥的通道。

  4. selector() - 此方法用于获取为其创建特定密钥的选择器。

  5. isValid() - 此方法返回密钥是否有效。

  6. isReadable() - 此方法说明密钥的通道是否已准备好读取。

  7. isWritable() - 此方法说明密钥的通道是否已准备好写入。

  8. isAcceptable() - 此方法说明密钥的通道是否已准备好接受传入的连接。

  9. isConnectable() - 此方法测试密钥的通道是否已完成或失败完成其 socket 连接操作。

  10. isAcceptable() - 此方法测试密钥的通道是否已准备好接受新的 socket 连接。

  11. interestOps() - 此方法检索此密钥的兴趣集。

  12. readyOps() - 此方法检索就绪集,它是通道已准备好的操作的集合。

通过调用其静态方法 select() ,我们可以从选择器中选择通道。选择器的选择方法被重载为 -

  1. select() -此方法阻塞当前线程,直到至少一个通道为其注册的事件准备好。

  2. select(long timeout) - 此方法与 select() 相同,但它最多阻塞线程 timeout 毫秒(参数)。

  3. selectNow() - 此方法根本不会阻塞。它立即返回任何就绪的通道。

此外,为了离开调用选择方法的阻塞线程,可以在选择器实例中调用 wakeup() 方法,然后在 select() 中等待的线程将立即返回。

最后,我们可以通过调用 close() 方法关闭选择器,该方法还会使与此选择器注册的所有 SelectionKey 实例无效,同时关闭选择器。

Example

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 - Pipe

在 Java NIO 中,管道是一个用于在两个线程之间写入和读取数据的组件。管道主要由负责数据传播的两个通道组成。

在这两个组成通道中,一个被称为接收通道,主要用于写入数据,另一个是源通道,其主要目的是从接收通道读取数据。

在数据写入和读取期间按顺序保持数据同步,必须确保以写入管道顺序读取数据。

必须注意,管道中的数据是单向流动的,即仅写入接收通道,只能从源通道读取数据。

在 Java NIO 中,管道被定义为一个抽象类,主要有三个方法,其中两个是抽象的。

Methods of Pipe class

  1. open() − 此方法用于获取 Pipe 实例,或者我们也可以说通过调用此方法创建 pipe。

  2. sink() − 此方法返回 Pipe 的 sink 通道,此通道用于通过调用其 write 方法写入数据。

  3. source() − 此方法返回 Pipe 的 source 通道,此通道用于通过调用其 read 方法读取数据。

Example

以下示例显示 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();
      }
   }
}

Output

Test Data to Check java NIO Channels Pipe.

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

Java NIO - Path

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

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

一般来说,实体的路经可以是两種類型,一种是绝对路徑,另一種是相对路徑。正如两种路徑的名称所示,绝对路徑是从根目錄到該实体所在位置的位置地址,而相對路徑是相对于其他一些路徑的位置地址。Path在其定义中使用分隔符,在 Windows 中使用“\”,而在 UNIX 操作系统中使用“/”。

为了获取 Path 的实例,可以使用 java.nio.file.Paths 类` get() `的静态方法。此方法将路经字符串或一串连接后形成路经字符串的字符串转换为 Path 实例。如果传递的参数包含非法字符,此方法还会抛出运行时 InvalidPathException。

如上所述,可以通过传递根元素和查找文件所需的完整目录列表来检索绝对路径。而可以通过将基路径与相对路径结合来检索相对路径。将在以下示例中说明如何检索这两个路径

Example

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

到目前为止,我们知道什么是路径接口,为什么我们需要它以及如何访问它。现在,我们会了解到路径接口为我们提供哪些重要的方法。

Important methods of Path Interface

  1. getFileName() − 返回创建此对象的的文件系统。

  2. getName() − 将此路径的一个名称元素作为 Path 对象返回。

  3. getNameCount() − 返回路径中的名称元素数。

  4. subpath() − 为此路径的名称元素的子序列返回相对路径。

  5. getParent() − 返回父路径,如果没有父路径,则返回一个 null。

  6. getRoot() − 将此路径的根组件作为 Path 对象返回,如果没有根组件,则返回一个 null。

  7. toAbsolutePath() − 返回表示此路径的绝对路径的 Path 对象。

  8. toRealPath() − 返回现有文件的真实路径。

  9. toFile() − 返回表示此路径的文件对象。

  10. normalize() − 返回此路径,其中已消除冗余的名称元素。

  11. compareTo(Path other) − 词汇比较两个抽象路径。如果参数等于此路径,则此方法返回零;如果此路径在词汇上小于参数,则返回小于零的值;如果此路径在词汇上大于参数,则返回大于零的值。

  12. endsWith(Path other) − 测试此路径是否以给定路径结束。如果给定路径有 N 个元素,没有根组件,并且此路径有 N 个或更多元素,则如果每个路径的最后 N 个元素(从最远离根的元素开始)相等,则此路径以给定路径结束。

  13. endsWith(String other) − 测试此路径是否以通过转换给定的路径字符串按 endsWith(Path) 方法指定的方式,而构造的路径结束。

Example

以下示例说明了上面提到的 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 - File

Java NIO 包提供了另一个实用程序 API 名为 Files,它基本上用于使用其主要用于 Path 对象的静态方法操作文件和目录。

如 Path 教程中所述,Path 接口是在 Java 7 版本的文件包中引入 Java NIO 包的。因此,此教程适用于相同的文件包。

此类完全包含对文件、目录或其他类型文件进行操作的静态方法。在大多数情况下,此处定义的方法会委托给关联的文件系统提供程序以执行文件操作。

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

Important methods of Files class.

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

  1. createFile(Path filePath, FileAttribute attrs) - Files 类提供了此方法来使用指定路径创建文件。

Example

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

Output

Created a file at : D:\data\file.txt
  1. copy(InputStream in, Path target, CopyOption… options) − 此方法用于将所有字节从指定输入流复制到指定目标文件,并以长值返回读取或写入的字节数。LinkOption 具有以下值的此参数 −

Example

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

Output

To be or not to be?
  1. createDirectories(Path dir, FileAttribute&lt;?&gt;&#8230;&#8203;attrs) - 此方法用于通过创建所有不存在的父目录来使用给定路径创建目录。

  2. delete(Path path) - 此方法用于从指定路径删除文件。如果文件不存在于指定路径或文件是目录且可能不为空并且无法删除,则抛出 NoSuchFileException。

  3. exists(Path path) - 此方法用于检查文件是否存在于指定路径,如果文件存在,则返回 true,否则返回 false。

  4. readAllBytes(Path path) - 此方法用于从给定路径的文件中读取所有字节,并返回一个包含从文件中读取的字节的字节数组。

Example

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

Output

Welcome to file.
  1. size(Path path) - 此方法用于以字节为单位获取指定路径处文件的大小。

  2. write(Path path, byte[] bytes, OpenOption… options) − 此方法用于将字节写入到指定路径的文件。

Example

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

Output

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 工作。

为了实现并发,此通道提供了两种方法,其中一种是返回 java.util.concurrent.Future object ,另一种是将 java.nio.channels.CompletionHandler 类型对象传递给操作。

我们将逐一借助示例了解这两种方法。

  1. Future Object − 在此方法中,一个 Future 接口的实例会从通道返回。在 Future 接口中,有 get() 方法,用于返回基于此方法异步处理的操作的状态,进而可以决定其他任务的进一步执行。我们还可以通过调用其 isDone 方法来检查任务是否已完成。

Example

以下示例显示如何使用 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();
   }
}

Output

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?
  1. Completion Handler − 此方法非常简单,就像我们使用 CompletionHandler 接口并覆盖其两个方法一样,一个方法为 completed() ,当 I/O 操作成功完成时调用此方法,另一个方法为 failed() ,当 I/O 操作失败时调用此方法。在这种情况下,会创建一个处理程序来使用异步 I/O 操作的结果,因为只有在任务完成后才执行处理程序的功能。

Example

以下示例显示如何使用 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();
   }
}

Output

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 中,每个字符都都有一个由 JVM 内部处理的明确定义的 unicode 代码单元。因此,Java NIO 包定义了一个名为 Charset 的抽象类,该类主要用于编码和解码字符集和 unicode。

Standard charsets

下面给出了 Java 中支持的 Charset。

  1. US-ASCII − 七位 ASCII 字符。

  2. ISO-8859-1 − ISO 拉丁字母。

  3. UTF-8 − 这是 8 位 UCS 转换格式。

  4. UTF-16BE − 这是 16 位 UCS 转换格式,具有大端字节顺序。

  5. UTF-16LE − 这是 16 位 UCS 转换,具有小端字节顺序。

  6. UTF-16 − 16 位 UCS 转换格式。

Important methods of Charset class

  1. forName() − 此方法为给定的字符集名称创建一个字符集对象。名称可以是规范名称或别名。

  2. displayName() − 此方法返回给定字符集的规范名称。

  3. canEncode() − 此方法检查给定字符集是否支持编码。

  4. decode() − 此方法将给定字符集的字符串解码为 Unicode 字符集的字符缓冲区。

  5. encode() − 此方法将 Unicode 字符集的字符缓冲区编码为给定字符集的字节缓冲区。

Example

以下示例说明了 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();
   }
}

Output

US-ASCII
Demo text for conversion.

Java NIO - FileLock

众所周知,Java NIO 支持并发和多线程,这使它能够同时处理在多个文件上运行的多个线程。但在某些情况下,我们需要文件不被任何线程共享,并且无法访问。

对于此类要求,NIO 再次提供了一个称为 FileLock 的 API,用于对整个文件或文件的一部分提供锁,以便文件或其部分不会被共享或访问。

为了提供或应用此类锁,我们必须使用 FileChannel 或 AsynchronousFileChannel,它们为此目的提供了两个方法,分别是 lock()tryLock()。提供的锁可以是两种类型之一:

  1. Exclusive Lock - 独占锁阻止其他程序获取任何类型的重叠锁。

  2. Shared Lock - 共享锁阻止其他并发运行的程序获取重叠的独占锁,但允许它们获取重叠的共享锁。

用于获取文件锁的方法:

  1. lock() − FileChannel 或 AsynchronousFileChannel 的这种方法获取与给定通道相关联的文件的独占锁。此方法的返回类型是 FileLock,用于进一步监控获取的锁。

  2. lock(long position, long size, boolean shared) − 此方法再次是 lock 方法的重载方法,用于锁定文件的特定部分。

  3. tryLock() − 此方法返回 FileLock 或 null(如果获取不到锁),它尝试获取此通道文件的显式独占锁。

  4. tryLock(long position, long size, boolean shared) − 此方法尝试获取此通道文件的指定区域的锁,该区域可能是独占类型或共享类型。

Methods of FileLock Class

  1. acquiredBy() − 此方法返回获取文件锁的通道。

  2. position() − 此方法返回已锁定区域的第一个字节在文件中的位置。已锁定的区域不必包含在实际基础文件中,甚至不必与之重叠,因此此方法返回的值可能超出文件的当前大小。

  3. size() − 此方法以字节为单位返回已锁定区域的大小。已锁定的区域不必包含在实际基础文件中,甚至不必与之重叠,因此此方法返回的值可能超出文件的当前大小。

  4. isShared() − 此方法用于确定锁是否共享。

  5. overlaps(long position,long size) − 此方法说明此锁是否与给定的锁范围重叠。

  6. isValid() − 此方法说明获取的锁是否有效。在释放锁或关闭关联文件通道(以先发生者为准)之前,锁对象将保持有效状态。

  7. release() − 释放获取的锁。如果锁对象有效,调用此方法将释放锁并将对象渲染为无效。如果此锁对象无效,则调用此方法不会产生任何效果。

  8. close() − 此方法调用 release() 方法。它被添加到类中,以便可以与自动资源管理块构造一起使用。

Example to demonstrate file lock.

下面的示例创建文件锁并在其中写入内容

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

Output

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.