1. 概要

このチュートリアルでは、Javaの大きなファイルからすべての行を効率的に読み取る方法を示します。

この記事は、ここBaeldungの「Java –基本に戻る」チュートリアルの一部です。

2. メモリ内の読み取り

ファイルの行を読み取る標準的な方法はメモリ内にあります。GuavaとApacheCommonsIOの両方が、まさにそれを行うための迅速な方法を提供します。

Files.readLines(new File(path), Charsets.UTF_8);
FileUtils.readLines(new File(path));

このアプローチの問題は、すべてのファイル行がメモリに保持されることです。ファイルが十分に大きい場合、すぐにOutOfMemoryErrorが発生します。

例– 〜1Gbファイルの読み取り

@Test
public void givenUsingGuava_whenIteratingAFile_thenWorks() throws IOException {
    String path = ...
    Files.readLines(new File(path), Charsets.UTF_8);
}

これは、少量のメモリが消費されることから始まります:(〜0 Mbが消費されます)

[main] INFO  org.baeldung.java.CoreJavaIoUnitTest - Total Memory: 128 Mb
[main] INFO  org.baeldung.java.CoreJavaIoUnitTest - Free Memory: 116 Mb

ただし、ファイル全体が処理された後、最後に(〜2 Gb消費)があります。

[main] INFO  org.baeldung.java.CoreJavaIoUnitTest - Total Memory: 2666 Mb
[main] INFO  org.baeldung.java.CoreJavaIoUnitTest - Free Memory: 490 Mb

これは、プロセスによって約2.1 Gbのメモリが消費されることを意味します(理由は単純です)。現在、ファイルの行はすべてメモリに保存されています。

この時点で、ファイルの内容をメモリに保持すると、実際のメモリの量に関係なく、使用可能なメモリがすぐに使い果たされることは明らかです。

さらに、通常、メモリ内のファイル内のすべての行を一度に必要とするわけではありません。代わりに、各行を繰り返し処理し、処理を行って破棄できるようにする必要があります。 つまり、これがまさに私たちがやろうとしていることです。すべての行をメモリに保持せずに、行を繰り返し処理します。

3. ファイルを介したストリーミング

次に、解決策を見てみましょう。 java .util.Scanner を使用して、ファイルの内容を実行し、行を1つずつ順番に取得します。

FileInputStream inputStream = null;
Scanner sc = null;
try {
    inputStream = new FileInputStream(path);
    sc = new Scanner(inputStream, "UTF-8");
    while (sc.hasNextLine()) {
        String line = sc.nextLine();
        // System.out.println(line);
    }
    // note that Scanner suppresses exceptions
    if (sc.ioException() != null) {
        throw sc.ioException();
    }
} finally {
    if (inputStream != null) {
        inputStream.close();
    }
    if (sc != null) {
        sc.close();
    }
}

このソリューションは、ファイル内のすべての行を反復処理し、各行を参照せずに処理できるようにします。結論として、メモリに保持せずに(〜150 Mb消費)

[main] INFO  org.baeldung.java.CoreJavaIoUnitTest - Total Memory: 763 Mb
[main] INFO  org.baeldung.java.CoreJavaIoUnitTest - Free Memory: 605 Mb

4. ApacheCommonsIOを使用したストリーミング

Commons IOライブラリを使用しても、ライブラリが提供するカスタムLineIterator を使用して、同じことを実現できます。

LineIterator it = FileUtils.lineIterator(theFile, "UTF-8");
try {
    while (it.hasNext()) {
        String line = it.nextLine();
        // do something with line
    }
} finally {
    LineIterator.closeQuietly(it);
}

ファイル全体が完全にメモリに格納されていないため、かなり控えめなメモリ消費数(〜150 Mb消費)になります。

[main] INFO  o.b.java.CoreJavaIoIntegrationTest - Total Memory: 752 Mb
[main] INFO  o.b.java.CoreJavaIoIntegrationTest - Free Memory: 564 Mb

5. 結論

この簡単な記事では、大きなファイルの行を繰り返し処理せずに、使用可能なメモリを使い果たすことなく処理する方法を示します。これは、これらの大きなファイルを操作するときに非常に便利です。

これらすべての例とコードスニペットの実装は、GitHubプロジェクトにあります。これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。