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ここで起きていることを、ゆっくり言葉にするとこうです。
Files.lines(path)で、「ファイルの行を順番に流すStream<String>」を取得try-with-resourcesでストリームを開き、最後に自動でクローズ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);
}
}
}
JavaStandardCharsets.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);
}
}
}
Javalimit(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);
}
}
Javalines で「行の Stream」、map で「整形/変換」、sum や count で「集計」
という流れは、非常に綺麗に書けます。
注意点:Stream の性質(使い捨て・中で例外が出たら?)
Stream は一度しか使えない
Files.lines が返す Stream<String> も、普通の Stream と同じで「使い捨て」です。
try (var lines = Files.lines(path)) {
long count = lines.count(); // ここでストリームを使い切る
// これは IllegalStateException になる(もう使えない)
lines.forEach(System.out::println);
}
Java1 回 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で包んで、ストリームを閉じる(=ファイルを閉じる)
