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

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

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

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

Java 1.1 ~ 1.3

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 を使う必要があります。これ自体はバッファしかしてくれません。

なので、結果として FileInputStreamInputStreamReaderBufferedReader とラップしていく必要があります。

Java 1.4 ~ 1.5

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

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 クラスが追加されました。これにより、わざわざ標準の文字コード1であれば文字列で指定せずに済むようになりました。
また、Files クラスと newBufferedReader(path, charset) メソッドが追加され、いちいちいろんなクラスを new する必要がなくなりました。2

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

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

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

Java 8 ~ 10

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 ~

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 が指定できるようになりました。 Java 1.1 のときに欲しかった…。

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

まとめ

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

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


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

  2. Files.newBufferedReader の中で、FileInputStreamInputStreamReaderBufferedReader とラップしてくれています。