NIO 2 AsynchronousFileChannel指南

2023/05/29

1. 概述

在本文中,我们将探讨Java 7中新I/O(NIO2)的关键附加API之一,即异步文件通道API。

你还可以阅读更多关于NIO.2文件操作路径操作的信息-理解这些将使本文更容易理解。

要在我们的项目中使用NIO2异步文件通道,我们必须导入java.nio.channels包,因为它捆绑了所有必需的类:

import java.nio.channels.*;

2. AsynchronousFileChannel

在本节中,我们将探讨如何使用使我们能够对文件执行异步操作的主类,即AsynchronousFileChannel类。要创建它的实例,我们调用静态open方法:

Path filePath = Paths.get("/path/to/file");

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(filePath, READ, WRITE, CREATE, DELETE_ON_CLOSE);

所有枚举值都来自StandardOpenOption。

open API的第一个参数是表示文件位置的Path对象。要了解有关NIO2中路径操作的更多信息,请访问此链接。其他参数组成一组指定选项,这些选项应该可用于返回的文件通道。

我们创建的异步文件通道可用于对文件执行所有已知操作。要仅执行操作的一个子集,我们将仅为那些操作指定选项。例如,只读:

Path filePath = Paths.get("/path/to/file");

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

3. 从文件中读取

就像NIO2中的所有异步操作一样,可以通过两种方式读取文件内容。使用Future和使用CompletionHandler。在每种情况下,我们都使用返回通道的read API。

在Maven的测试资源文件夹或源目录中(如果不使用Maven),让我们创建一个名为file.txt的文件,其开头只有文本tuyucheng.com。我们现在将演示如何读取此内容。

3.1 Future方法

首先,我们将看到如何使用Future类异步读取文件:

@Test
public void givenFilePath_whenReadsContentWithFuture_thenCorrect() {
    Path path = Paths.get(URI.create(this.getClass().getResource("/file.txt").toString()));
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

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

    // run other code as operation continues in background
    operation.get();
      
    String fileContent = new String(buffer.array()).trim();
    buffer.clear();

    assertEquals(fileContent, "tuyucheng.com");
}

在上面的代码中,在创建文件通道后,我们使用了read API-它需要一个ByteBuffer来存储从通道读取的内容作为它的第一个参数。

第二个参数是long值,表示从文件中哪个位置开始读取。

无论文件是否已被读取,该方法都会立即返回。

接下来,我们可以在后台继续操作时执行任何其他代码。当我们执行完其他代码时,我们可以调用get() API,如果我们正在执行其他代码时操作已经完成,它会立即返回,否则它会阻塞直到操作完成。

我们的断言确实证明文件中的内容已被读取。

如果我们将read API调用中的位置参数从0更改为其他值,我们也会看到效果。例如,字符串tuyucheng.com中的第7个字符是n。因此,将位置参数更改为7会导致缓冲区包含字符串ng.com。

3.2 CompletionHandler方法

接下来,我们将看到如何使用CompletionHandler实例读取文件的内容:

@Test
public void givenPath_whenReadsContentWithCompletionHandler_thenCorrect() {
    Path path = Paths.get(URI.create( this.getClass().getResource("/file.txt").toString()));
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    fileChannel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {

        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            // result is number of bytes read
            // attachment is the buffer containing content
        }
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {

        }
    });
}

在上面的代码中,我们使用了read API的第二个变体。它仍然以一个ByteBuffer和读取操作的起始位置分别作为第一个和第二个参数。第三个参数是CompletionHandler实例。

CompletionHandler的第一个泛型类型是操作的返回类型,在本例中是一个整数,表示读取的字节数。

第二个是附件的类型。我们选择附加缓冲区,以便在read完成时,我们可以在completed的回调API中使用文件的内容。

从语义上讲,这不是一个真正的有效单元测试,因为我们不能在completed的回调方法中进行断言。但是,我们这样做是为了保持一致性,因为我们希望我们的代码尽可能复制粘贴运行。

4. 写入文件

Java NIO2还允许我们对文件执行写操作。正如我们对其他操作所做的那样,我们可以通过两种方式写入文件。使用Future和使用CompletionHandler。在每种情况下,我们都使用返回通道的write API。

创建用于写入文件的AsynchronousFileChannel可以像这样完成:

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

4.1 特别注意事项

请注意传递给open API的选项。我们还可以添加另一个选项StandardOpenOption.CREATE如果我们希望创建由路径表示的文件以防它不存在。另一个常见选项是StandardOpenOption.APPEND,它不会覆盖文件中的现有内容。

我们将使用以下行来创建用于测试目的的文件通道:

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, WRITE, CREATE, DELETE_ON_CLOSE);

这样,我们将提供任意路径并确保将创建该文件。测试退出后,创建的文件将被删除。为确保创建的文件在测试退出后不被删除,你可以删除最后一个选项。

要运行断言,我们需要在写入文件后尽可能读取文件内容。让我们将读取逻辑隐藏在一个单独的方法中以避免冗余:

public static String readContent(Path file) {
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(file, StandardOpenOption.READ);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

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

    // run other code as operation continues in background
    operation.get();     

    String fileContent = new String(buffer.array()).trim();
    buffer.clear();
    return fileContent;
}

4.2 Future方法

使用Future类异步写入文件:

@Test
public void givenPathAndContent_whenWritesToFileWithFuture_thenCorrect() {
    String fileName = UUID.randomUUID().toString();
    Path path = Paths.get(fileName);
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, WRITE, CREATE, DELETE_ON_CLOSE);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    buffer.put("hello world".getBytes());
    buffer.flip();

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

    //run other code as operation continues in background
    operation.get();

    String content = readContent(path);
    assertEquals("hello world", content);
}

让我们检查一下上面的代码中发生了什么。我们创建一个随机文件名并使用它来获取Path对象。我们使用此路径通过前面提到的选项打开异步文件通道。

然后我们将要写入文件的内容放在一个缓冲区中,然后执行write。我们使用我们的辅助方法来读取文件的内容,并确认它是我们所期望的。

4.3 CompletionHandler方法

我们还可以使用CompletionHandler,这样我们就不必在while循环中等待操作完成:

@Test
public void givenPathAndContent_whenWritesToFileWithHandler_thenCorrect() {
    String fileName = UUID.randomUUID().toString();
    Path path = Paths.get(fileName);
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, WRITE, CREATE, DELETE_ON_CLOSE);

    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("hello world".getBytes());
    buffer.flip();

    fileChannel.write(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {

        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            // result is number of bytes written
            // attachment is the buffer
        }
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {

        }
    });
}

这次我们调用write API时,唯一的新事物是第三个参数,我们在其中传递类型为CompletionHandler的匿名内部类。

当操作完成时,该类调用它的completed方法,我们可以在其中定义应该发生的事情。

5. 总结

在本文中,我们探讨了Java NIO2的异步文件通道API的一些最重要的功能。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章