Javaのバージョン別、テキストファイルを一括で読み込む方法まとめ

前回の記事(Javaのバージョン別、1行ずつファイルを読む方法まとめ)への感想で、このような話がありました。

というわけで、今回はテキストファイルを一括で読み込む方法をまとめました。
(前回と被っている点は省略しているので、まだ読んでない人は先に前回の記事をどうぞ)

Java 1.1, 1.2, 1.3

private static String readString(File file) throws IOException {
    Reader reader = null;
    try {
        reader = new InputStreamReader(new FileInputStream(file), "UTF-8");

        StringBuffer sb = new StringBuffer();
        int len;
        char[] buffer = new char[1024 * 8];
        while ((len = reader.read(buffer)) != -1) {
            sb.append(buffer, 0, len);
        }
        return sb.toString();
    } finally {
        if (reader != null) {
            reader.close();
        }
    }
}

まだこのころはめんどくさいです。

FileInputStream でファイルを読み込み、InputStreamReader で指定した文字コードにデコードするようにしています。
読み込む際は、char[] でバッファして StringBuffer に追加していっています。

ちなみに、char[] でバッファしながら読み込んでいるので、 BufferedReader でラップする必要はありません。

Java 1.4, 1.5, 6

private static String readString(File file) throws IOException {
    CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();

    try (FileInputStream stream = new FileInputStream(file)) {
        FileChannel channel = stream.getChannel();
        ByteBuffer bb = channel.map(MapMode.READ_ONLY, 0, channel.size());

        CharBuffer cb = decoder.decode(bb);

        return cb.toString();
    }
}

ループがなくなって、だいぶ処理がすっきりしました。

このバージョンで追加された Charset クラスから、CharsetDecoder というデコーダを取得できます。
これに FileChannel#map​(FileChannel.MapMode mode, long position, long size) で取得した MappedByteBuffer を渡すことで、ファイルを読み込めます。

MappedByteBuffer はファイルの内容をメモリにマッピングして扱う(メモリマップドファイル)ので、大きなファイルの時に効率的に処理が行われます。
逆に言うと、小さいファイルを読み込む場合は性能が悪いです。
その場合は、今まで通り InputStream で読み込みましょう。

ほとんどのオペレーティング・システムでは、ファイルをメモリーマッピングするほうが、通常のreadメソッドまたはwriteメソッドを使って数十キロバイトのデータの読み込みまたは書込みを行うよりも負荷が大きくなります。 性能を重視するなら、比較的大きめのファイルだけをマッピングすることをお薦めします。 FileChannel (Java SE 15 & JDK 15))

----
ちなみに、この FileChannelmap メソッドを使うやり方はひしだまさんの記事を読んで知りました!
Javaバッファークラスメモ(Hishidama's Java Buffer Memo)

Java 7, 8, 9, 10

private static String readString(Path path) throws IOException {
    byte[] bytes = Files.readAllBytes(path);
    
    return new String(bytes, StandardCharsets.UTF_8);
}

Files クラスが追加され、これに一括でファイルを byte[] として読み込むメソッドが用意されました。
これを使ってファイルを読み込めば、あとは String クラスを new するだけです。

ちなみに、このバージョンでファイルからすべての行を読み取る Files#readAllLines​(Path, Charset) も追加されました。
この戻り値は List<String> です。行ごとに処理をしたい場合はこちらが便利です。

Java 11~

private static String readString(Path path) throws IOException {
    return Files.readString(path, StandardCharsets.UTF_8);
}

ひとつのメソッドでファイルをすべて読み込めるようになりました。 1

このメソッドの実装では StringCoding というインターナルなクラスを使っているので、new String(bytes, StandardCharsets.UTF_8) とするよりも効率的な処理になっています。

まとめ

めんどくさかった処理も、Java のバージョンが上がるごとに簡単に書けるようになっています。
なので、積極的に新しい Java を使っていきましょう!

関連記事


  1. Google で検索して一番上に出てくる JavaDocJava 8 なせいで、この Java 11 で追加されたメソッドを忘れられがちです…。