Javaで大きなファイルを効率的に読み込む方法

Javaで大きなファイルを効率的に読み取る方法

1. 概要

このチュートリアルでは、how to read all the lines from a large file in Javaを効率的に表示します。

この記事は、例としてここのthe “Java – Back to Basic” tutorialの一部です。

参考文献:

Java – InputStreamをファイルに書き込む

InputStreamをファイルに書き込む方法-Java、Guava、およびCommons IOライブラリを使用します。

Java –ファイルをInputStreamに変換

JavaファイルからInputStreamを開く方法-プレーンJava、Guava、およびApache Commons IOライブラリを使用します。

Java –ファイルから読み取る

Javaのファイルからコンテンツを読み取ります-BufferedReader、Scanner、StreamTokenizer、DataInputStream、SequenceInputStream、FileChannelなどのいずれかを使用します。

2. メモリ内の読み取り

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

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

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

例–reading a ~1Gb file

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

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

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

ただし、after the full file has been processed、最後に次のようになります:(~2 Gb consumed)

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

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

この時点で、実際の量に関係なく、keeping in memory the contents of the file will quickly exhaust the available memoryであることは明らかです。

さらに、we usually don’t need all of the lines in the file in memory at once –代わりに、それぞれを反復処理し、処理を実行して破棄できるようにする必要があります。 つまり、これがまさに私たちがやろうとしていることです。メモリに保持せずに行を繰り返します。

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();
    }
}

このソリューションは、ファイル内のすべての行を繰り返し処理し、各行を参照せずに処理できるようにします。結論として、without keeping them in memory(~150 Mb consumed)

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

4. Apache CommonsIOを使用したスト​​リーミング

ライブラリによって提供されるthe custom LineIteratorを使用することにより、CommonsIOライブラリを使用しても同じことが実現できます。

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

ファイル全体が完全にメモリにあるわけではないため、これもpretty conservative memory consumption numbersになります:(~150 Mb consumed)

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

5. 結論

この簡単な記事では、process lines in a large file without iteratively, without exhausting the available memoryの実行方法を示しています。これは、これらの大きなファイルを操作するときに非常に役立ちます。

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