Java FileChannelのガイド

1. 概要

このクイックチュートリアルでは、https://で提供される_https://docs.oracle.com/javase/8/docs/api/java/nio/channels/FileChannel.html [FileChannel] _クラスを参照します。 docs.oracle.com/javase/8/docs/api/java/nio/package-summary.html[_Java NIO_]ライブラリ。 * _FileChannel_および_ByteBuffer_ *を使用してデータを読み書きする方法について説明します。
また、_FileChannel_およびその他のファイル操作機能を使用する利点についても説明します。

2. _FileChannel_の利点

_FileChannel_の利点は次のとおりです。
  • ファイル内の特定の位置での読み取りと書き込み

  • ファイルのセクションをメモリに直接ロードします。
    効率的

  • あるチャネルから別のチャネルに、より高速でファイルデータを転送できます。

  • ファイルのセクションをロックして、他のスレッドによるアクセスを制限できます

  • データの損失を防ぐために、ファイルへの更新の書き込みをすぐに強制することができます
    ストレージへ

3. _FileChannel_を使用した読み取り

大きなファイルを読み取る場合、_FileChannel_は標準のI / Oよりも高速に実行されます。
_Java NIO_の一部ではありますが、* _ FileChannel_操作はブロック*であり、非ブロックモードはありません。

3.1. _FileChannel_を使用したファイルの読み取り

以下を含むファイルで_FileChannel_を使用してファイルを読み取る方法を理解しましょう。
Hello world
このテストはファイルを読み取り、正常に読み取られたことを確認します。
@Test
public void givenFile_whenReadWithFileChannelUsingRandomAccessFile_thenCorrect()
  throws IOException {
    try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r");
        FileChannel channel = reader.getChannel();
        ByteArrayOutputStream out = new ByteArrayOutputStream()) {

        int bufferSize = 1024;
        if (bufferSize > channel.size()) {
           bufferSize = (int) channel.size();
        }
        ByteBuffer buff = ByteBuffer.allocate(bufferSize);

        while (channel.read(buff) > 0) {
            out.write(buff.array(), 0, buff.position());
            buff.clear();
        }

     String fileContent = new String(out.toByteArray(), StandardCharsets.UTF_8);

     assertEquals("Hello world", fileContent);
    }
}
ここでは、_FileChannel _、_ RandomAccessFile_、および_ByteBuffer._を使用してファイルからバイトを読み取ります。
*複数の同時スレッドが安全に** _ FileChannels_を使用できることに注意する必要があります。 **ただし、一度に1つのスレッドのみが、チャネルの位置の更新またはファイルサイズの変更を伴う操作を許可されます。 これにより、前の操作が完了するまで、同様の操作を試みる他のスレッドがブロックされます。
ただし、明示的なチャネル位置を提供する操作は、ブロックされることなく同時に実行できます。

3.2. _FileChannel_を開く

_FileChannel_を使用してファイルを読み取るには、ファイルを開く必要があります。
__FileChannel __using _RandomAccessFile_を開く方法を見てみましょう。
RandomAccessFile reader = new RandomAccessFile(file, "r");
FileChannel channel = reader.getChannel();
*モード「r」は、チャネルが「読み取り専用」であることを示します。
次に、_FileInputStream_を使用してファイルを読み取るために_FileChannel_を開きます。
FileInputStream fin= new FileInputStream(file);
FileChannel channel = fin.getChannel();
同様に、_FileInputStream_を閉じると、それに関連付けられているチャネルも閉じます。

3.3. _FileChannel_からのデータの読み取り

データを読み取るには、いずれかの_read_メソッドを使用できます。
バイトシーケンスを読み取る方法を見てみましょう。 _ByteBuffer_を使用してデータを保持します。
ByteBuffer buff = ByteBuffer.allocate(1024);
int noOfBytesRead = channel.read(buff);
String fileContent = new String(buff.array(), StandardCharsets.UTF_8);

assertEquals("Hello world", fileContent);
次に、ファイル位置から始まるバイトシーケンスの読み取り方法を説明します。
ByteBuffer buff = ByteBuffer.allocate(1024);
int noOfBytesRead = channel.read(buff, 5);
String fileContent = new String(buff.array(), StandardCharsets.UTF_8);
assertEquals("world", fileContent);
*バイト配列を_String_ *にデコードするには、_Charset_の必要性に注意する必要があります。
バイトが最初にエンコードされた_Charset_を指定します。 it __、__がなければ、テキストが文字化けする可能性があります。 特に、_UTF-8_や_UTF-16_などのマルチバイトエンコーディングは、一部のマルチバイト文字が不完全なため、ファイルの任意のセクションをデコードできない場合があります。

4. _FileChannel_を使用した書き込み

4.1. _FileChannel_を使用したファイルへの書き込み

_FileChannel_を使用して記述する方法を調べてみましょう。
@Test
public void whenWriteWithFileChannelUsingRandomAccessFile_thenCorrect()
  throws IOException {
    String file = "src/test/resources/test_write_using_filechannel.txt";
    try (RandomAccessFile writer = new RandomAccessFile(file, "rw");
        FileChannel channel = writer.getChannel()){
        ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8));

        channel.write(buff);

     // verify
     RandomAccessFile reader = new RandomAccessFile(file, "r");
     assertEquals("Hello world", reader.readLine());
     reader.close();
    }
}

4.2. _FileChannel_を開く

_FileChannel_を使用してファイルに書き込むには、ファイルを開く必要があります。
__FileChannel __using _RandomAccessFile_を開く方法を見てみましょう。
RandomAccessFile writer = new RandomAccessFile(file, "rw");
FileChannel channel = writer.getChannel();
*モード「rw」は、チャネルが「読み取りおよび書き込み用に開いている」ことを示します。*
_FileOutputStream_を使用して_FileChannel_を開く方法も見てみましょう。
FileOutputStream fout = new FileOutputStream(file);
FileChannel channel = fout.getChannel();

4.3. _FileChannel_を使用したデータの書き込み

_FileChannel_を使用してデータを書き込むには、_write_メソッドのいずれかを使用できます。
_ByteBuffer_を使用してデータを保存し、バイトシーケンスを書き込む方法を見てみましょう。
ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8));
channel.write(buff);
次に、ファイル位置から始まる一連のバイトを書き込む方法を説明します。
ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8));
channel.write(buff, 5);

5. 現在位置

_FileChannel_を使用すると、読み取りまたは書き込みの位置を取得および変更できます。
現在の位置を取得する方法を見てみましょう。
long originalPosition = channel.position();
次に、位置の設定方法を見てみましょう。
channel.position(5);
assertEquals(originalPosition + 5, channel.position());

6. ファイルのサイズを取得する

_https://docs.oracle.com/javase/8/docs/api/java/nio/channels/FileChannel.html#size-- [FileChannel.size] _メソッドを使用してサイズを取得する方法を見てみましょう。バイト単位のファイル:
@Test
public void whenGetFileSize_thenCorrect()
  throws IOException {
    RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r");
    FileChannel channel = reader.getChannel();

    // the original file size is 11 bytes.
    assertEquals(11, channel.size());

    channel.close();
    reader.close();
}

7. ファイルを切り捨てる

_https://docs.oracle.com/javase/8/docs/api/java/nio/channels/FileChannel.html#truncate-long- [FileChannel.truncate] _メソッドを使用してファイルを切り捨てる方法を理解しましょう指定されたバイト単位のサイズ:
@Test
public void whenTruncateFile_thenCorrect()
  throws IOException {
    String input = "this is a test input";

    FileOutputStream fout = new FileOutputStream("src/test/resources/test_truncate.txt");
    FileChannel channel = fout.getChannel();

    ByteBuffer buff = ByteBuffer.wrap(input.getBytes());
    channel.write(buff);
    buff.flip();

    channel = channel.truncate(5);
    assertEquals(5, channel.size());

    fout.close();
    channel.close();
}

8. ストレージへのファイル更新を強制する

オペレーティングシステムは、パフォーマンス上の理由でファイルの変更をキャッシュし、システムがクラッシュするとデータが失われる場合があります。 ファイルのコンテンツとメタデータを強制的にディスクに連続的に書き込むには、_force_メソッドを使用できます。
channel.force(true);
この方法は、ファイルがローカルデバイスにある場合にのみ保証されます。

9. ファイルのセクションをメモリにロードする

_https://docs.oracle.com/javase/8/docs/api/java/nio/channels/FileChannel.html#map-java.nio.channels.FileChannelを使用して、メモリ内のファイルのセクションをロードする方法を見てみましょう.MapMode-long-long- [FileChannel.map] ._ https://docs.oracle.com/javase/8/docs/api/java/nio/channels/FileChannel.MapMode.html[_FileChannel.MapModeを使用します。 READ_ONLY_]読み取り専用モードでファイルを開くには:
@Test
public void givenFile_whenReadAFileSectionIntoMemoryWithFileChannel_thenCorrect()
  throws IOException {
    try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r");
        FileChannel channel = reader.getChannel();
        ByteArrayOutputStream out = new ByteArrayOutputStream()) {

        MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 6, 5);

        if(buff.hasRemaining()) {
          byte[] data = new byte[buff.remaining()];
          buff.get(data);
          assertEquals("world", new String(data, StandardCharsets.UTF_8));
        }
    }
}
同様に、_FileChannel.MapMode.READ_WRITE_を使用して、ファイルを読み取りモードと書き込みモードの両方で開くことができます。
また、__ * FileChannel.MapMode.PRIVATE * __ * modeを使用することもできます。この場合、変更は元のfile __.__ *には適用されません

10. ファイルのセクションをロックする

https://docs.oracle.com/javase/8/docs/api/java/nio/channels/FileChannel.html#tryLock-longを使用してセクションの同時アクセスを防ぐためにファイルのセクションをロックする方法を理解しましょう-long-boolean-[_ FileChannel.tryLock_] method:
@Test
public void givenFile_whenWriteAFileUsingLockAFileSectionWithFileChannel_thenCorrect()
  throws IOException {
    try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "rw");
        FileChannel channel = reader.getChannel();
        FileLock fileLock = channel.tryLock(6, 5, Boolean.FALSE )){

        //do other operations...

        assertNotNull(fileLock);
    }
}
_tryLock_メソッドは、ファイルセクションのロックを取得しようとします。 要求されたファイルセクションが別のスレッドによってすでにブロックされている場合、_https://docs.oracle.com/javase/8/docs/api/java/nio/channels/OverlappingFileLockException.html [OverlappingFileLockException] _例外がスローされます。 このメソッドは、_boolean_パラメーターを使用して、共有ロックまたは排他ロックを要求します。
一部のオペレーティングシステムは共有ロックを許可せず、代わりに排他ロックを使用する場合があることに注意してください。

11. _FileChannel_を閉じる

最後に、_FileChannel_の使用が終了したら、それを閉じる必要があります。 この例では、_https://www.baeldung.com/java-try-with-resources [try-with-resources] ._を使用しました。
必要に応じて、_close_メソッドで__FileChannel ___directを直接閉じることができます。
channel.close();

12. 結論

このチュートリアルでは、* FileChannel_を使用してファイルを読み書きする方法*を見てきました。 さらに、ファイルサイズとその現在の読み取り/書き込み場所を読み取って変更する方法を検討し、同時またはデータクリティカルなアプリケーションで_FileChannels_を使用する方法を検討しました。
いつものように、サンプルのソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-nio[GitHubで]から入手できます。