A Guide To NIO2 Asynchronous File Channel – NIO2异步文件通道指南

最后修改: 2016年 12月 6日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we are going to explore one of the key additional APIs of the new I/O (NIO2) in Java 7, asynchronous file channel APIs.

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

If you are new to asynchronous channel APIs in general, we have an introductory article on this site which you can read by following this link before proceeding.

如果你对一般的异步通道API感到陌生,我们在这个网站上有一篇介绍性的文章,你可以在继续之前按照这个链接进行阅读。

You can read more about NIO.2 file operations and path operations as well – understanding these will make this article much easier to follow.

你可以阅读更多关于NIO.2 文件操作路径操作的内容 – 了解这些将使这篇文章更容易理解。

To use the NIO2 asynchronous file channels in our projects, we have to import the java.nio.channels package as it bundles all required classes:

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

import java.nio.channels.*;

2. The AsynchronousFileChannel

2.异步文件通道

In this section, we will explore how to use the main class that enables us to perform asynchronous operations on files, the AsynchronousFileChannel class. To create an instance of it, we call the static open method:

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

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

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

All enum values come from the StandardOpenOption.

所有枚举值都来自StandardOpenOption

The first parameter to the open API is a Path object representing the file location. To read more about path operations in NIO2, follow this link. The other parameters make up a set specifying options that should be available to the returned file channel.

打开API的第一个参数是一个代表文件位置的Path对象。要阅读关于NIO2中的路径操作的更多信息,请遵循这个链接。其他参数构成了一个指定选项的集合,这些选项应该对返回的文件通道可用。

The asynchronous file channel we have created can be used to perform all known operations on a file. To perform only a subset of the operations, we would specify options for only those. For instance, to only read:

我们创建的异步文件通道可以用来对一个文件进行所有已知的操作。为了只执行一个子集的操作,我们将只为这些操作指定选项。例如,只读。

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

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

3. Reading From a File

3.从文件中读取

Just like with all asynchronous operations in NIO2, reading a file’s contents can be done in two ways. Using Future and using CompletionHandler. In each case, we use the read API of the returned channel.

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

Inside the test resources folder of maven or in the source directory if not using maven, let’s create a file called file.txt with only the text baeldung.com at it’s beginning. We will now demonstrate how to read this content.

在maven的测试资源文件夹下,如果不使用maven,则在源目录下,让我们创建一个名为file.txt的文件,文件开头只有文字baeldung.com。现在我们将演示如何读取这些内容。

3.1. The Future Approach

3.1.未来的方法

First, we will see how to read a file asynchronously using the Future class:

首先,我们将看到如何使用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, "baeldung.com");
}

In the above code, after creating a file channel, we make use of the read API – which takes a ByteBuffer to store the content read from the channel as its first parameter.

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

The second parameter is a long indicating the position in the file from which to start reading.

第二个参数是一个长字符串,表示从文件的哪个位置开始读。

The method returns right away whether the file has been read or not.

该方法立即返回文件是否已被读取。

Next, we can execute any other code as the operation continues in the background. When we are done with executing other code, we can call the get() API which returns right away if the operation already completed as we were executing other code, or else it blocks until the operation completes.

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

Our assertion indeed proves that the content from the file has been read.

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

If we had changed the position parameter in the read API call from zero to something else, we would see the effect too. For example, the seventh character in the string baeldung.com is g. So changing the position parameter to 7 would cause the buffer to contain the string g.com.

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

3.2. The CompletionHandler Approach

3.2.完成处理程序方法

Next, we will see how to read a file’s contents using a CompletionHandler instance:

接下来,我们将看到如何使用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) {

        }
    });
}

In the above code, we use the second variant of the read API. It still takes a ByteBuffer and the start position of the read operation as the first and second parameters respectively. The third parameter is the CompletionHandler instance.

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

The first generic type of the completion handler is the return type of the operation, in this case, an Integer representing the number of bytes read.

完成处理程序的第一个通用类型是操作的返回类型,在本例中是一个代表读取字节数的整数。

The second is the type of the attachment. We have chosen to attach the buffer such that when the read completes, we can use the content of the file inside the completed callback API.

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

Semantically speaking, this is not really a valid unit test since we cannot do an assertion inside the completed callback method. However, we do this for the sake of consistency and because we want our code to be as copy-paste-run-able as possible.

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

4. Writing to a File

4.向文件写入

Java NIO2 also allows us to perform write operations on a file. Just as we did with other operations, we can write to a file in two ways. Using Future and using CompletionHandler. In each case, we use the write API of the returned channel.

Java NIO2还允许我们对文件进行写操作。就像我们对其他操作一样,我们可以通过两种方式向文件写入。使用Future和使用CompletionHandler。在每种情况下,我们都使用返回通道的writeAPI。

Creating an AsynchronousFileChannel for writing to a file can be done like this:

创建一个AsynchronousFileChannel以写入一个文件,可以这样做。

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

4.1. Special Considerations

4.1.特殊考虑

Notice the option passed to the open API. We can also add another option StandardOpenOption.CREATE if we want the file represented by a path to be created in case it does not already exist. Another common option is StandardOpenOption.APPEND which does not over-write existing content in the file.

注意传递给open API的选项。我们还可以添加另一个选项StandardOpenOption.CREATE,如果我们希望由path代表的文件在不存在的情况下被创建。另一个常见的选项是StandardOpenOption.APPEND,它不会改写文件中的现有内容。

We will use the following line for creating our file channel for test purposes:

我们将使用下面的行来创建我们的文件通道,用于测试目的。

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

This way, we will provide any arbitrary path and be sure that the file will be created. After the test exits, the created file will be deleted. To ensure the files created are not deleted after the test exits, you can remove the last option.

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

To run assertions, we will need to read the file content where possible after writing to them. Let’s hide the logic for reading in a separate method to avoid redundancy:

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

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. The Future Approach

4.2.未来的方法

To write to a file asynchronously using the Future class:

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

Let’s inspect what is happening in the above code. We create a random file name and use it to get a Path object. We use this path to open an asynchronous file channel with the previously mentioned options.

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

We then put the content we want to write to the file in a buffer and perform the write. We use our helper method to read the contents of the file and indeed confirm that it is what we expect.

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

4.3. The CompletionHandler Approach

4.3.完成处理程序方法

We can also use the completion handler so that we don’t have to wait for the operation to complete in a while loop:

我们也可以使用完成处理程序,这样我们就不必在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) {

        }
    });
}

When we call the write API this time, the only new thing is a third parameter where we pass an anonymous inner class of type CompletionHandler.

当我们这次调用写入API时,唯一的新东西是第三个参数,我们传递一个匿名的CompletionHandler类型的内部类。

When the operation completes, the class calls it’s completed method within which we can define what should happen.

当操作完成后,该类会调用它的完成方法,在该方法中我们可以定义应该发生什么。

5. Conclusion

5.结论

In this article, we have explored some of the most important features of the Asynchronous File Channel APIs of Java NIO2.

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

To get all code snippets and the full source code for this article, you can visit the Github project.

要获得本文的所有代码片段和完整的源代码,你可以访问Github项目