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

1概要

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

一般的に非同期チャネルAPIに慣れていない場合は、このサイトに紹介記事があります。リンクを読んで先に進むことができます。

NIO.2のリンクについてもっと読むことができます:/java-nio-2-file-api[ファイル操作]と パス操作 - これらを理解することはこの記事をずっと簡単にするでしょうフォローする。

私たちのプロジェクトで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);

すべてのenum値はSta __ndardOpenOption __から来ます。

open APIの最初のパラメータはファイルの場所を表す Path オブジェクトです。 NIO2でのパス操作の詳細については、リンク:/java-nio-2-path[このリンク]をたどってください。他のパラメータは、返されたファイルチャンネルに利用可能であるべきオプションを指定するセットを構成します。

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

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

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

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

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

mavenのテストリソースフォルダ内、またはmavenを使用していない場合はソースディレクトリ内に、先頭に baeldung.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<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");
}

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

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

ファイルが読み込まれたかどうかにかかわらず、メソッドはすぐに戻ります。

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

私たちの主張は確かにファイルの内容が読まれたことを証明します。

read API呼び出しのpositionパラメーターをゼロから別のものに変更した場合は、その効果も見られます。たとえば、文字列 baeldung.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<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の2番目のバリアントを使用しています。それはまだ ByteBuffer read 操作の開始位置をそれぞれ1番目と2番目のパラメータとして取ります。 3番目のパラメータは CompletionHandler インスタンスです。

完了ハンドラの最初のジェネリック型は操作の戻り型、この場合は読み込まれたバイト数を表す整数です。

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

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

4ファイルへの書き込み

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

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

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

4.1. 特別な考慮事項

open APIに渡されたオプションに注目してください。 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<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 クラスを使用してファイルに非同期的に書き込むには、次のようにします。

@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 アプローチ

補完ハンドラを使用して、操作が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 型の匿名内部クラスを渡す3番目のパラメータです。

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

5結論

この記事では、Java NIO2のAsynchronous File Channel APIの最も重要な機能のいくつかを調べました。

この記事のすべてのコードスニペットと完全なソースコードを入手するには、https://github.com/eugenp/tutorials/tree/master/core-java-io[Githubプロジェクト]にアクセスしてください。