在 Java 7 中,Java NIO 新增了 AsynchronousFileChannel 类。AsynchronousFileChannel 类使得数据可以进行异步读写。本文将介绍 AsynchronousFileChannel 的使用。


阅读文章的过程中如果有任何疑问,欢迎添加笔者为好友,拉您进【七日书摘】微信交流群,一起交流技术,一起打造高质量的职场技术交流圈子,抱团取暖,共同进步。
七日书摘官方群.jpg

创建 AsynchronousFileChannel(Creating an AsynchronousFileChannel)

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

Path path = Paths.get("data/test.xml");

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

open() 方法的第一个参数是一个 Path 实例,指向需要操作的文件。

第二个参数是一个或多个打开操作类型选项,告诉 AsynchronousFileChannel 要对文件执行那些类型的操作。上述示例中,我们使用 StandardOpenOption.READ,表示以读的形式操作文件。

读取数据(Reading Data)

可以通过两种方式从 AsynchronousFileChannel 中读取数据。每种方法都会调用 AsynchronousFileChannel 的一个 read() 方法。下面将分别介绍这两种写法。

通过 Future 读取数据(Reading Data Via a Future)

AsynchronousFileChannel 读取数据的第一种方法是调用返回 Future 的 read() 方法。下面是调用 read() 方法的方式:

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

该版本的 read() 方法将 ByteBuffer 作为第一个参数,从 AsynchronousFileChannel 中读取的数据会被读入到 ByteBuffer 中。第二个参数是开始读取数据的字节位置。

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

下面是一个略长的示例,演示该版本的 read() 方法的使用:

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。调用 fileChannel.read(buffer, position) 后创建了一个循环来检查 Future 的 isDone() 方法返回是否为 true(即检查是否读取完毕)。当然,这不是一个非常有效的CPU使用-但不知何故,你需要等到读操作完成。

读取操作完成后,将数据读入 ByteBuffer,然后读入字符串并使用 System.out 输出。

通过 CompletionHandler 读取数据(Reading Data Via a CompletionHandler)

从 AsynchronousFileChannel 读取数据的第二种方法是调用以 CompletionHandler 作为参数的 read() 方法版本。下面是示例如何调用 read() 方法:

fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("result = " + 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() 方法。并传入一个 Integer 和 ByteBuffer 参数,前面的 Integer 表示是读取到的字节数大小,第二 ByteBuffer 也可以换成其他合适的对象方便数据写入。

如果读取操作失败了,将会调用 CompletionHandlerfailed() 方法。

写数据(Writing Data)

与读取一样,您可以用两种方式将数据写入 AsynchronousFileChannel ,写入数据时调用不同的 write() 方法。以下部分将介绍两种数据写入方法。

通过 Future 写数据(Writing Data Via a Future)

通过 AsynchronousFileChannel 可以异步写入数据。下面是一个 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("test data".getBytes());
buffer.flip();

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

while(!operation.isDone());

System.out.println("Write done");

首先 AsynchronousFileChannel 以写模式打开,接着创建一个 ByteBuffer 并将一些数据写入其中,再把 ByteBuffer 中的数据写入文件。最后检查返回的 Future 写入操作是否已完成。

这里需要注意,该文件(本作者注:data/test-write.txt)必须是已经存在的,然后以上代码才能正常工作,否则 write() 方法将抛出java.nio.file.NoSuchFileException

检查文件是否存在可以通过以下的示例代码实现:

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

通过 CompletionHandler 写数据(Writing Data Via a CompletionHandler)

你还可以使用 CompletionHandlerAsynchronousFileChannel 写入数据,该方法可以明确知道何时写入完成,而不像使用 Future 写入数据的方式那样需要自己进行检查。

下面是使用 CompletionHandler 将数据写入AsynchronousFileChannel 的示例:

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("test data".getBytes());
buffer.flip();

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

    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("bytes written: " + result);
    }

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

和读取数据类似,当写入操作完成时,将调用 CompletionHandlercompleted() 方法。如果由于某种原因写入操作失败,将调用 failed() 方法。

注意:ByteBuffer 是如何传递给 CompletionHandler 对象的方法的。

英文原文链接:http://tutorials.jenkov.com/java-nio/asynchronousfilechannel.html

------完------

推荐阅读:

Java NIO 简明教程 之 Java NIO 数据报通道(DatagramChannel)

Java NIO 简明教程 之 Java NIO 管道(Pipe)

Java NIO 简明教程 之 Java NIO vs. IO

Java NIO 简明教程 之 路径(Path)

Java NIO 简明教程 之 Java NIO 文件(Files)

Java基础知识面试题篇(2020年2月最新版)

更技术学习请进入七日书摘官方群: 七日书摘官方群

七日书摘官方群群聊二维码.png

参考资源:
https://wiki.jikexueyuan.com/project/java-nio-zh/java-nio-asynchronousfilechannel.html