Javaのバージョン別、1行ずつファイルを読む方法まとめ

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

Java でファイルを読み込む処理は、バージョンが上がるごとにどんどん簡単に書けるようになっていきました。
今回は、どれだけ簡単になっていったかを Java のバージョンごとにまとめて説明します。

なお、ここでは以下の処理を行うコードをもとにしています。

* そこそこ大きいテキストファイルを一行ずつ読み込む
* 文字コードは `UTF-8`


# Java 1.1, Java 1.2, Java 1.3

```java
public static void main(String[] args) throws IOException {
    File file = new File(args[0]);
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(
                    new InputStreamReader(
                            new FileInputStream(file)
                            , "UTF-8"));
        
        String line;
        while((line = reader.readLine()) != null) {
            // 処理
        }
    } finally {
        if (reader != null) {
            reader.close();
        }
    }
}
```

長いですね。

この時代に `FileReader` というファイルからテキストを読み込むクラスがあるにはあります。
ただ、このクラスでは **問答無用でシステムデフォルトの文字コードが使われてしまいます。**
そのため、Windows から Linux に持っていった場合に文字化けが起きる問題がよく起こっていました。

それを避けるためには、文字コードの指定ができる `InputStreamReader` を使う必要があります。
`InputStreamReader` 自体ではファイルの読み込みができないので、`FileInputStream` を使う必要があります。

さらに、一行ずつ読み込むためには `BufferedReader` を使う必要があります。これ自体はバッファしかしてくれません。

なので、結果として `FileInputStream` → `InputStreamReader` → `BufferedReader` とラップしていく必要があります。

# Java 1.4, Java 1.5, Java 6

```java
public static void main(String[] args) throws IOException {
    File file = new File(args[0]);
    Charset charset = Charset.forName("UTF-8");

    BufferedReader reader = null;
    try {
        reader = new BufferedReader(
                    new InputStreamReader(
                        new FileInputStream(file), charset));

        String line;
        while ((line = reader.readLine()) != null) {
            // 処理
        }
    } finally {
        if (reader != null) {
            reader.close();
        }
    }
}
```

 `Charset` クラスができました。
これにより、文字コードを型で表せるようになりました。

ただ、`UTF-8` を使う場合には `Charset.forName("UTF-8")` とする必要がありました。
なので、型安全ではあるけれど、文字列で指定した方が楽という状況でした。

# Java 7

```java

public static void main(String[] args) throws IOException {
    Path path = Paths.get(args[0]);
    Charset charset = StandardCharsets.UTF_8;

    try (BufferedReader reader = Files.newBufferedReader(path, charset)){
        String line;
        while ((line = reader.readLine()) != null) {
            // 処理
        }
    }
}
```


一気にシンプルになりました。

まず、`StandardCharsets` クラスが追加されました。これにより、標準の文字コード[^charset]であればわざわざ文字列で指定せずに済むようになりました。
また、`Files` クラスと `newBufferedReader(path, charset)` メソッドが追加され、いろんなクラスを `new` する必要がなくなりました。[^Files]

あと、このバージョンで `Path` クラスが追加されました。
ファイルを読み込むだけだと `File` クラスと大差ないですが、パスの操作が簡単になりました。

さらに、`try-with-resources` が追加され、めんどくさかった `close` 処理をいい感じにやってくれるようになりました。

－－－－
ちなみに…。
このバージョンから まとめて読み込んで `List<String>` に格納してくれる `Files.readAllLines​(Path, Charset)` も使えるようになりました。
ただし、これは大きなファイルに使うとメモリを食ってしまうので要注意です。

[^charset]: `US-ASCII`, `ISO-8859-1`, `UTF-8`, `UTF-16BE`, `UTF-16LE`, `UTF-16`。これらは、どの Java 実装でも必ずサポートされています。

[^Files]: `Files.newBufferedReader` の中で、`FileInputStream` → `InputStreamReader` → `BufferedReader` とラップしてくれています。

# Java 8, Java 9, Java 10

```java
public static void main(String[] args) throws IOException {
    Path path = Paths.get(args[0]);

    try (Stream<String> stream = Files.lines(path)){
        stream.forEach(line -> /* 処理 */);
    }
}
```

`Stream` クラスによって、行の読み込みと処理を反復させることができるようになりました。
これのおかげで、1行ずつ処理をしたいという場合にとてもシンプルに書けるようになりました。

また、`Files` クラスを使う際に、`UTF-8` であれば文字コードの指定を省略できるようになりました。
**`InputStreamReader` などは省略時にシステムのデフォルト文字コードが指定されたものとみなされますが、こちらは `UTF-8` を指定したものとみなされる** という点には注意が必要です。
ただ、最近のテキストファイルが `UTF-8` であることを踏まえると、妥当な判断かなと思います。

# Java 11 ～

```java
public static void main(String[] args) throws IOException {
    Path path = Path.of(args[0]);

    try (Stream<String> stream = Files.lines(path)){
        stream.forEach(line -> /* 処理 */);
    }
}
```

`Path.of​(String first, String... more)` というメソッドが追加されました。
内部の処理は `Paths.get(String first, String... more)` とまったく同じですが、このおかげで  `Path` クラスを使うにはどうすればいいんだっけと迷わずに済むようになりました。

－－－－
上記のサンプルでは使っていないのですが…。
**なんと、このバージョンで [`FileReader` クラスのコンストラクタで `Charset` が指定できるようになりました。](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/FileReader.html#%3Cinit%3E%28java.lang.String,java.nio.charset.Charset%29)**
Java 1.1 のときに欲しかった…。


なお、`InputStreamReader` クラスのように文字コードを "UTF-8" のような文字列で指定することはできません。
既存のクラスは互換性のために文字列でも `Charset` でも指定できるようになっていますが、今後追加されるクラスは `Charset` のみになるようです。

# まとめ

Java はファイルを読み込むだけでもなんて面倒くさいんだと言われていました。
でも、それは昔のお話。

今ではとても簡単になっているんだよ、というのが伝われば幸いです。

# 関連記事

* [Javaのバージョン別、テキストファイルを一括で読み込む方法まとめ - Qiita](https://qiita.com/YujiSoftware/items/cac5f34a1b4ea1d2d434)
* [Java の + 演算子による文字列結合は、どのように処理されているのか - Qiita](https://qiita.com/YujiSoftware/items/62505944e49edf95fa11)