Java | Java 標準ライブラリ:Files.lines

Java Java
スポンサーリンク

Files.lines を一言でいうと

Files.lines は、

「テキストファイルを 1 行ずつ“ストリームとして”読み出すためのメソッド」
です。

Files.readAllLines が「全部いっぺんに List<String> に読む」のに対して、
Files.lines は「必要な分だけ、順番に流しながら処理する」スタイルになります。

特に、

  • ファイルが大きい(全部メモリに載せたくない)
  • 行をフィルタしたり、変換したり、集計したい

といった場面で、Stream<String> として扱える Files.lines はとても強力です。


基本の使い方と「Stream<String>」という戻り値の意味

最もシンプルな例

import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;
import java.util.stream.Stream;

public class FilesLinesBasic {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("data.txt");

        try (Stream<String> lines = Files.lines(path)) {
            lines.forEach(System.out::println);
        }
    }
}
Java

ここで起きていることを、ゆっくり言葉にするとこうです。

  1. Files.lines(path) で、「ファイルの行を順番に流す Stream<String>」を取得
  2. try-with-resources でストリームを開き、最後に自動でクローズ
  3. forEach で、1 行ずつ取り出して表示

大事なのは、「この時点で、まだファイル全部を読み込んでいるわけではない」ということです。
ストリームの処理が進むにつれて、必要な行が順番に読み出されていくイメージを持ってください。

なぜ try-with-resources が必要か

Files.lines が返す Stream<String> は、
内部的には「ファイルを開いたリソース」に紐づいています。

ストリームの処理が終わったら、
必ず close() してファイルを閉じる必要があります。

そのため、

try (Stream<String> lines = Files.lines(path)) {
    // ここで stream を使う
}
Java

のように、try-with-resources で包む書き方が標準です。


Files.readAllLines との違い(ここをしっかり理解する)

readAllLines:全部読み込んで List にする

Files.readAllLines(path) は、

  • ファイルの全行を一気に読み込む
  • List<String> としてメモリに全部載せる

という動きです。

コードはシンプルになりますが、大きなファイルだとメモリをたくさん食います。

lines:必要な行だけ順次読む(遅延評価)

Files.lines(path) は、

  • Stream<String> として「行の流れ」を返す
  • ストリームを処理していく中で、必要な行だけ逐次読み出す
  • ファイル全体を一度にメモリに保持しない

という動きになります。

イメージとしては、

  • readAllLines …… 「全部まとめて持ってきてから、あとで処理」
  • lines …… 「必要な分だけ、読みながら処理を進める」

です。

ファイルが大きいときや、
「途中で条件が満たされたら、残りは読まなくていい」ような処理では、
lines のほうが圧倒的に有利です。


文字コード(エンコーディング)を指定する

デフォルトでは「環境の文字コード」

Files.lines(path) だけ書いたときは、
OS のデフォルト文字コード(Windows なら CP932、Linux なら UTF-8 など)で読み込みます。

これは Files.readAllLines と同じです。

「自分の PC だけで動けばいい」なら問題になりにくいですが、
他環境でも動かすことを考えると、文字コードは明示しておいた方が安全です。

UTF-8 を明示する例

import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.io.IOException;
import java.util.stream.Stream;

public class FilesLinesUtf8 {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("data.txt");

        try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
            lines.forEach(System.out::println);
        }
    }
}
Java

StandardCharsets.UTF_8 を使うのが定番です。

readAllLines と同様、

  • Files.lines(path) …… 環境依存
  • Files.lines(path, UTF_8) …… どこでも同じ挙動

という差があります。


Stream と組み合わせた実用パターン

例1:条件に合う行だけを数える

data.txt の中から、「ERROR を含む行の数」を数えたいとします。

import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.io.IOException;

public class CountErrorLines {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("app.log");

        long errorCount;
        try (var lines = Files.lines(path, StandardCharsets.UTF_8)) {
            errorCount = lines
                    .filter(line -> line.contains("ERROR"))
                    .count();
        }

        System.out.println("ERROR 行数: " + errorCount);
    }
}
Java

ここでやっているのは、

  • 1 行ずつ流して
  • “ERROR” を含むかどうかでフィルタし
  • 最後に件数を数える

という処理です。

readAllLines で全部 List にしてから stream() しても同じことはできますが、
lines なら「読みながら Stream の世界で完結」できます。

特にログファイルのように大きくなりがちなデータでは、
「全部読み込まない」というメリットは大きいです。

例2:最初の N 行だけを見る(limit)

ファイルの先頭 10 行だけを表示したいとき。

import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.io.IOException;

public class HeadExample {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("data.txt");

        try (var lines = Files.lines(path, StandardCharsets.UTF_8)) {
            lines
                .limit(10)                        // 最初の10行だけ
                .forEach(System.out::println);
        }
    }
}
Java

limit(10) が効いているので、
ファイルが何万行あっても 読み出すのは最初の 10 行だけ です。

これも、lines が「遅延評価で必要な分だけ読む」おかげです。

例3:マッピングして集計する

例えば scores.txt がこんな内容だとします。

10
20
30
40

これを全部足し合わせたい。

import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.io.IOException;

public class SumScores {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("scores.txt");

        int sum;
        try (var lines = Files.lines(path, StandardCharsets.UTF_8)) {
            sum = lines
                    .map(String::trim)                  // 前後の空白を消す
                    .filter(s -> !s.isEmpty())         // 空行は除外
                    .mapToInt(Integer::parseInt)       // int に変換
                    .sum();                            // 合計
        }

        System.out.println("合計: " + sum);
    }
}
Java

lines で「行の Stream」、
map で「整形/変換」、
sumcount で「集計」
という流れは、非常に綺麗に書けます。


注意点:Stream の性質(使い捨て・中で例外が出たら?)

Stream は一度しか使えない

Files.lines が返す Stream<String> も、普通の Stream と同じで「使い捨て」です。

try (var lines = Files.lines(path)) {
    long count = lines.count();          // ここでストリームを使い切る

    // これは IllegalStateException になる(もう使えない)
    lines.forEach(System.out::println);
}
Java

1 回 count()forEach() を呼んだら、その Stream は終わりです。
もう一度読みたいなら、Files.lines(path) をもう一度呼んで、新しい Stream を作ります。

中で例外が出たら?

Integer.parseInt などでパースエラーが出た場合、
NumberFormatException がそこで投げられ、
try-with-resources の外へ飛んでいきます。

IOException 自体は、Files.lines を呼んだときや、
ストリームを実際に評価したときに発生する可能性があります。

実戦では、

try (var lines = Files.lines(path, UTF_8)) {
    // lines を使った処理
} catch (IOException e) {
    // 「ファイルを開けない」「読み込めない」などのエラー処理
}
Java

という構造で、
「I/O 系」と「パース系」のエラーを分けて考えると分かりやすいです。


Files.lines を使うときに意識しておきたいこと

どんなときに lines を選ぶか

次のような条件がそろうとき、Files.lines は特に有効です。

  • ファイルが大きくなりうる
  • 行をフィルタしたり、変換したり、集計したい(Stream と相性が良い)
  • 「先頭だけ」「条件に合う行だけ」など、全部を読む必要がない

逆に、小さな設定ファイルなどで、
「全部 List で欲しいだけ」なら、readAllLines のほうがシンプルなことも多いです。

必ず try-with-resources で閉じる

Files.lines の地味に大事なルールはこれです。

try (Stream<String> lines = Files.lines(path)) {
    ...
}
Java

これを忘れて、単に

Stream<String> lines = Files.lines(path);
lines.forEach(...);
Java

と書くと、
ファイルが閉じられずにリソースリークの原因になります。

「ファイルやソケットに紐づいた Stream は、必ず try-with-resources」という感覚を、今のうちに身体に入れておいてください。


まとめ:Files.lines を自分の中でこう位置づける

Files.lines を初心者向けにまとめると、こうなります。

  • テキストファイルを「1 行ずつ流れる Stream<String>」として扱えるメソッド
  • 遅延評価で、必要になった行だけ読み出す(大きなファイルに向いている)
  • filter / map / limit / count / forEach など、Stream の操作と相性が良い
  • 文字コードはできるだけ StandardCharsets.UTF_8 などを明示する
  • 必ず try-with-resources で包んで、ストリームを閉じる(=ファイルを閉じる)

タイトルとURLをコピーしました