NIO2非同期ファイルチャンネルの手引き

NIO2非同期ファイルチャネルのガイド

1. 概要

この記事では、Java 7の新しいI / O(NIO2)の主要な追加APIの1つ、非同期ファイルチャネルAPIについて説明します。

一般に非同期チャネルAPIを初めて使用する場合は、このサイトに紹介記事があり、先に進む前にthis linkをたどることで読むことができます。

NIO.2file operationspath operationsについても詳しく読むことができます。これらを理解すると、この記事をより簡単に理解できるようになります。

プロジェクトで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から取得されます。

オープンAPIの最初のパラメーターは、ファイルの場所を表すPathオブジェクトです。 NIO2でのパス操作の詳細については、this linkに従ってください。 他のパラメーターは、返されたファイルチャネルで使用できるオプションを指定するセットを構成します。

作成した非同期ファイルチャネルを使用して、ファイルに対するすべての既知の操作を実行できます。 操作のサブセットのみを実行するには、それらのオプションのみを指定します。 たとえば、読み取り専用:

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

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

3. ファイルからの読み取り

NIO2のすべての非同期操作と同様に、ファイルの内容の読み取りは2つの方法で実行できます。 FutureCompletionHandlerを使用します。 いずれの場合も、返されたチャネルのreadAPIを使用します。

mavenのテストリソースフォルダー内、またはmavenを使用していない場合はソースディレクトリに、先頭にテキストexample.comのみが含まれるfile.txtというファイルを作成しましょう。 このコンテンツの読み方を示します。

3.1. 将来のアプローチ

最初に、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 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, "example.com");
}

上記のコードでは、ファイルチャネルを作成した後、read APIを使用します。これはチャネルから読み取られたコンテンツを最初のパラメーターとして格納するためにByteBufferを取ります。

2番目のパラメーターは、読み取りを開始するファイル内の位置を示すlongです。

このメソッドは、ファイルが読み取られたかどうかに関係なくすぐに戻ります。

次に、バックグラウンドで操作が続行されるときに、他のコードを実行できます。 他のコードの実行が終了したら、get() APIを呼び出すことができます。このAPIは、他のコードを実行していたときに操作がすでに完了している場合はすぐに返され、そうでない場合は操作が完了するまでブロックされます。

実際、私たちの主張は、ファイルのコンテンツが読み取られたことを証明しています。

read API呼び出しの位置パラメーターをゼロから別の何かに変更した場合、効果も確認できます。 たとえば、文字列example.comの7番目の文字はgです。 したがって、positionパラメーターを7に変更すると、バッファーに文字列g.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() {

        @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) {

        }
    });
}

上記のコードでは、readAPIの2番目のバリアントを使用しています。 それでも、ByteBufferread操作の開始位置をそれぞれ最初と2番目のパラメーターとして使用します。 3番目のパラメーターはCompletionHandlerインスタンスです。

完了ハンドラーの最初の汎用タイプは、操作の戻り値のタイプです。この場合、読み取られたバイト数を表す整数です。

2番目は添付ファイルのタイプです。 readが完了したときに、completedコールバックAPI内のファイルのコンテンツを使用できるようにバッファーをアタッチすることを選択しました。

意味的に言えば、completedコールバックメソッド内でアサーションを実行できないため、これは実際には有効な単体テストではありません。 ただし、これは一貫性を保つため、およびコードを可能な限りcopy-paste-run-ableにするために行います。

4. ファイルへの書き込み

Java NIO2では、ファイルに対して書き込み操作を実行することもできます。 他の操作で行ったように、2つの方法でファイルに書き込むことができます。 FutureCompletionHandlerを使用します。 いずれの場合も、返されたチャネルのwriteAPIを使用します。

ファイルに書き込むためのAsynchronousFileChannelの作成は、次のように実行できます。

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

4.1. 特別な考慮事項

openAPIに渡されたオプションに注意してください。 pathで表されるファイルがまだ存在しない場合に作成する場合は、別のオプションStandardOpenOption.CREATEを追加することもできます。 もう1つの一般的なオプションは、ファイル内の既存のコンテンツを上書きしない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 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 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アプローチ

完了ハンドラーを使用して、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() {

        @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) {

        }
    });
}

今回書き込みAPIを呼び出すとき、唯一の新しいことは、タイプCompletionHandlerの匿名内部クラスを渡す3番目のパラメーターです。

操作が完了すると、クラスはそれが完了したメソッドを呼び出します。このメソッド内で、何が起こるかを定義できます。

5. 結論

この記事では、Java NIO2の非同期ファイルチャネルAPIの最も重要な機能のいくつかを検討しました。

この記事のすべてのコードスニペットと完全なソースコードを入手するには、Github projectにアクセスしてください。