Java NIO 中的 AsynchronousFileChannel(异步文件通道)支持对文件进行异步读写操作,显著提升I/O效率。它提供两种数据读写模式:一是基于Future接口,调用后立即返回,可通过轮询检查操作状态;二是基于CompletionHandler回调,在操作完成或失败时自动触发相应方法。创建通道时需指定Path和操作选项(如READ/WRITE)。使用时需注意写入前确保目标文件存在,并通过ByteBuffer传递数据。该机制适用于需避免I/O阻塞的高性能应用场景。

博主博客

在 Java 7 中,Java NIO 新增了 AsynchronousFileChannel(异步文件通道)。它允许以异步方式从文件中读取数据或向文件写入数据。本教程将详细介绍如何使用 AsynchronousFileChannel。

创建 AsynchronousFileChannel

通过静态方法 open() 可以创建一个 AsynchronousFileChannel。以下是一个创建示例:

Path path = Paths.get("data/test.xml");
AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.READ);

open() 方法的第一个参数是一个 Path 实例,指向 AsynchronousFileChannel 要关联的文件。

第二个参数是一个或多个打开选项,用于指定对底层文件执行的操作。上面的例子中使用了 StandardOpenOption.READ,表示以只读方式打开文件。

读取数据

有两种方式可以从 AsynchronousFileChannel 读取数据,每种方式都调用了 read() 方法。下面将分别介绍这两种方法。

通过 Future 读取数据

第一种方式是调用返回 Futureread() 方法。调用方式如下:

Future<Integer> operation = fileChannel.read(buffer, 0);

这个版本的 read() 方法以 ByteBuffer 作为第一个参数。从 AsynchronousFileChannel 读取的数据会存入该 ByteBuffer 中。第二个参数是文件开始读取的字节位置。

即使读取操作尚未完成,read() 方法也会立即返回。可以通过调用 read() 方法返回的 Future 实例的 isDone() 方法来检查读取操作是否完成。

下面是一个更完整的示例:

AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.READ);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

Future<Integer> operation = fileChannel.read(buffer, position);

while(!operation.isDone()); // 等待读取完成

buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
buffer.clear();

这个例子创建了一个 AsynchronousFileChannel,然后创建了一个 ByteBuffer 并将其作为参数传递给 read() 方法,同时指定起始位置为 0。调用 read() 后,程序循环检查返回的 Future 的 isDone() 方法是否返回 true。请注意,这种循环等待的方式对 CPU 效率不高,但我们需要某种方式来等待读取操作完成。

一旦读取操作完成,数据会从 ByteBuffer 中提取出来,转换为字符串并打印到控制台。

通过 CompletionHandler 读取数据

第二种方式是通过调用接收 CompletionHandler 作为参数的 read() 方法来读取数据。调用方式如下:

fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("读取的字节数 = " + result);
        
        attachment.flip();
        byte[] data = new byte[attachment.limit()];
        attachment.get(data);
        System.out.println(new String(data));
        attachment.clear();
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        // 处理读取失败的情况
    }
});

读取操作完成后,CompletionHandler 的 completed() 方法会被调用。completed() 方法的参数包括一个表示读取字节数的 Integer,以及传递给 read() 方法的“附件”(attachment)。在本例中,“附件”是用于存储数据的 ByteBuffer。你可以自由选择要附加的对象。

如果读取操作失败,CompletionHandler 的 failed() 方法将被调用。

写入数据

与读取数据类似,向 AsynchronousFileChannel 写入数据也有两种方式。每种方式都调用了 write() 方法。下面将分别介绍这两种方法。

通过 Future 写入数据

AsynchronousFileChannel 也支持异步写入数据。以下是一个完整的写入示例:

Path path = Paths.get("data/test-write.txt");
AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

buffer.put("测试数据".getBytes());
buffer.flip();

Future<Integer> operation = fileChannel.write(buffer, position);
buffer.clear();

while(!operation.isDone()); // 等待写入完成

System.out.println("写入完成");

首先以写入模式打开一个 AsynchronousFileChannel。然后创建一个 ByteBuffer,并向其中写入一些数据。接着将 ByteBuffer 中的数据写入文件。最后通过检查返回的 Future 来确定写入操作何时完成。

注意:文件必须已经存在,否则 write() 方法会抛出 java.nio.file.NoSuchFileException 异常。

可以使用以下代码确保文件存在:

if(!Files.exists(path)) {
    Files.createFile(path);
}

通过 CompletionHandler 写入数据

除了使用 Future,你还可以使用 CompletionHandler 来通知写入操作何时完成。示例如下:

Path path = Paths.get("data/test-write.txt");
if(!Files.exists(path)) {
    Files.createFile(path);
}
AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

buffer.put("测试数据".getBytes());
buffer.flip();

fileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("写入的字节数: " + result);
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        System.out.println("写入失败");
        exc.printStackTrace();
    }
});

写入操作完成时,CompletionHandler 的 completed() 方法会被调用。如果写入失败,则会调用 failed() 方法。

注意示例中如何将 ByteBuffer 作为“附件”传递给 CompletionHandler 的方法。

参考文献