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

（この記事は [地平線に行く](https://yujisoftware.hatenablog.com/entry/2021/05/04/014600) とのマルチポストです） 

前回の記事（[Javaのバージョン別、1行ずつファイルを読む方法まとめ - Qiita](https://qiita.com/YujiSoftware/items/5975c0a93f5e58f6b9cf)）への感想で、このような話がありました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">丸々読み込む方法のまとめもお願いします！</p>&mdash; ユースケ (山本裕介) (@yusuke) <a href="https://twitter.com/yusuke/status/1389045214229123074?ref_src=twsrc%5Etfw">May 3, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

というわけで、今回はテキストファイルを一括で読み込む方法をまとめました。
（前回と被っている点は省略しているので、まだ読んでない人は先に[前回の記事](https://qiita.com/YujiSoftware/items/5975c0a93f5e58f6b9cf)をどうぞ）

# Java 1.1, 1.2, 1.3

```java
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

```java
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`](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/nio/charset/CharsetDecoder.html) というデコーダを取得できます。
これに `FileChannel#map​(FileChannel.MapMode mode, long position, long size)` で取得した `MappedByteBuffer` を渡すことで、ファイルを読み込めます。

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

> ほとんどのオペレーティング・システムでは、ファイルをメモリーにマッピングするほうが、通常のreadメソッドまたはwriteメソッドを使って数十キロバイトのデータの読み込みまたは書込みを行うよりも負荷が大きくなります。 性能を重視するなら、比較的大きめのファイルだけをマッピングすることをお薦めします。 
> [FileChannel (Java SE 15 & JDK 15)](https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/nio/channels/FileChannel.html#map(java.nio.channels.FileChannel.MapMode,long,long))

－－－－
この `FileChannel` の `map` メソッドを使うやり方はひしだまさんの記事を読んで知りました！
[Javaバッファークラスメモ(Hishidama's Java Buffer Memo)](https://www.ne.jp/asahi/hishidama/home/tech/java/Buffer.html#h_FileChannel)

# Java 7, 8, 9, 10

```java
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～

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

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

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

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

# まとめ

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

# 関連記事

* [Javaのバージョン別、1行ずつファイルを読む方法まとめ - Qiita](https://qiita.com/YujiSoftware/items/5975c0a93f5e58f6b9cf)
* [Java の + 演算子による文字列結合は、どのように処理されているのか - Qiita](https://qiita.com/YujiSoftware/items/62505944e49edf95fa11)