1. 概述

本文将深入探讨 Java 7 中 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);

所有枚举值来自 StandardOpenOptionopen 方法的参数说明:

  • 第一个参数Path 对象,表示文件位置
  • 后续参数:文件通道的可用选项集合

✅ 支持的常用操作:

  • 读写文件
  • 创建文件
  • 关闭时自动删除

❌ 注意:若只需部分操作,可指定对应选项。例如只读文件:

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

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

3. 读取文件内容

NIO2 提供两种异步读取方式:FutureCompletionHandler。假设测试资源目录下存在 file.txt,内容为 baeldung.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);

    // 执行其他代码,读取操作在后台进行
    operation.get();
      
    String fileContent = new String(buffer.array()).trim();
    buffer.clear();

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

关键点解析:

  1. read 方法参数:
    • ByteBuffer:存储读取内容
    • long:文件起始读取位置(0 表示开头)
  2. read 立即返回,不阻塞线程
  3. operation.get() 阻塞直到操作完成
  4. ⚠️ 修改位置参数会影响结果:如设为 7,将读取 g.com(第 7 个字符是 'g')

3.2. 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: 读取的字节数
            // attachment: 包含内容的缓冲区
        }
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {

        }
    });
}

核心机制:

  • 第三个参数是 CompletionHandler 实例
  • 泛型参数:
    • Integer:操作返回类型(字节数)
    • ByteBuffer:附加对象类型(这里传递缓冲区)
  • 操作完成后自动调用 completed 方法
  • ⚠️ 此示例无法直接断言,仅作演示用

4. 写入文件内容

NIO2 同样支持异步写入,同样提供 FutureCompletionHandler 两种方式。

4.1. 特殊注意事项

创建写入通道时的关键选项:

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

✅ 常用选项组合:

  • CREATE:文件不存在时自动创建
  • APPEND:追加内容而非覆盖
  • DELETE_ON_CLOSE:关闭时删除文件(测试场景推荐)

测试用通道创建示例:

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

    // 执行其他代码,读取操作在后台进行
    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();

    // 执行其他代码,写入操作在后台进行
    operation.get();

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

执行流程:

  1. 生成随机文件名
  2. 创建带自动清理的文件通道
  3. 准备缓冲区数据(注意 flip() 切换读写模式)
  4. 异步写入后通过 get() 等待完成
  5. 验证写入内容

4.3. CompletionHandler 方式

使用回调机制处理写入结果:

@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: 写入的字节数
            // attachment: 使用的缓冲区
        }
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {

        }
    });
}

核心差异:

  • 第三个参数传递 CompletionHandler 实例
  • 写入完成后自动触发 completed 回调
  • 无需手动阻塞等待操作完成

5. 总结

本文系统介绍了 Java NIO2 中异步文件通道的核心特性:

  • ✅ 两种异步操作模式:FutureCompletionHandler
  • ✅ 灵活的文件操作选项(读/写/创建/删除)
  • ✅ 高效的非阻塞 I/O 处理

完整代码示例可访问 GitHub 项目。实际开发中,根据场景选择合适的方式:

  • 需要精确控制时用 Future
  • 需要事件驱动时用 CompletionHandler

原始标题:A Guide To NIO2 Asynchronous File Channel