Java | Java 標準ライブラリ:BufferedReader

Java Java
スポンサーリンク

BufferedReader を一言でいうと

BufferedReader は、

「文字をまとめて内部バッファに貯めながら、“行単位で”効率よく読み取るための Reader」

です。

ファイルやネットワークから「テキスト」を読むときに、

  • 1 文字ずつチマチマ読むのは遅い
  • 自分でバッファ処理を書くのは面倒

これを全部引き受けてくれるのが BufferedReader です。

特に、

  • readLine() で「1 行ずつ読む」
  • 内部でまとめ読みしてくれるので高速

この 2 点が本質的な役割です。


BufferedReader の基本イメージとよくある組み合わせ

「Reader をラップして高機能にする」クラス

BufferedReader は単体では使わず、
他の Reader を包んで使うラッパーです。

一番よく見るのはファイル読み込みのパターンです。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderBasic {
    public static void main(String[] args) {
        try (BufferedReader reader =
                     new BufferedReader(new FileReader("data.txt"))) {

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Java

ここで使われているものを分解すると、

  • FileReader …… ファイルから文字を読む「低レベルな Reader」
  • BufferedReader …… その Reader をラップして、「バッファ+行読み」を提供

という役割分担になっています。

あなたが主に意識するのは、

BufferedReader reader = new BufferedReader(何かの Reader);
reader.readLine() で 1 行読む

という 2 点です。

InputStream と組み合わせる場合

InputStream(バイト単位)から直接 BufferedReader は作れません。
間に InputStreamReader が必要です。

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

public class FromInputStream {
    public static void main(String[] args) throws Exception {
        try (InputStream in = new FileInputStream("data.txt");
             BufferedReader reader = new BufferedReader(
                     new InputStreamReader(in, StandardCharsets.UTF_8))) {

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}
Java

バイト列 → 文字(Reader)→ BufferedReader(行単位・バッファ付き)
という流れです。


なぜ「バッファ付き(Buffered)」が重要なのか

1 文字ずつ読むのは遅い

Reader には read() という「1 文字だけ読む」メソッドがあります。

int ch = reader.read();  // 1文字だけ
Java

これを何万・何十万回も繰り返すと、
一回ごとに OS とやりとりが発生して、とても遅くなります。

そこで、

  • ある程度のサイズの塊(バッファ)を一気に読み込む
  • プログラムからは、そのバッファからちょっとずつ取り出す

という仕組みが必要になります。

それをやってくれるのが BufferedReader です。

BufferedReader の中で起きていること(イメージ)

頭の中のイメージとしては、こんな感じです。

  1. OS から「ある程度大きめの文字配列」を一回で読み込む(まとめ読み)
  2. read()readLine() は、そのバッファから必要な分だけ取り出す
  3. バッファが尽きたら、また OS からまとめて読み足す

こうすることで、

  • OS とのやりとり回数が減る
  • プログラムからはシンプルな readLine() で済む

という両取りができます。

あなたは「バッファを内部で良い感じにやってくれているんだな」と思っておけば十分です。


readLine() による「1 行ずつ読み取り」が本命機能

readLine() の基本

BufferedReader が一気に便利になるメソッドが readLine() です。

String line = reader.readLine();
Java

これが返すものは、

  • 行末の改行コードを「含まない」文字列
  • ファイルの終わり(EOF)に達したら null

です。

典型的な「1 行ずつ全部読む」パターンはこうなります。

String line;
while ((line = reader.readLine()) != null) {
    // ここで1行分の文字列を処理する
}
Java

これは、ファイルやネットワークからテキストを読むときの超定番パターンです。

改行コード(\n, \r\n など)の違いを意識しなくていい

OS によって改行コードは違います。

  • Unix / macOS …… \n
  • Windows …… \r\n

readLine() は、この違いをよしなに吸収してくれます。

どの OS のファイルであっても、

  • 改行コードを取り除いた「行の中身だけ」が返る
  • 次の readLine() で、次の行が返る

という動作になります。

あなたは「1 行ずつ文字列が取れる」とだけ覚えておけば大丈夫です。


Files.readAllLines / Files.lines との関係

BufferedReader を直接使うケースと、Files を使うケース

最近の Java だと、
BufferedReader を直接使らずに Files 経由で済ませることも増えました。

全部読み込んで List にしたいとき:

List<String> lines = Files.readAllLines(path, UTF_8);
Java

ストリームとして 1 行ずつ処理したいとき:

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

これらのメソッドも、内部では BufferedReader によく似た仕組みを使っています。

一方で、

  • もっと細かく制御したいとき
  • Java 7 以前のコードを読むとき
  • 教科書的な「ファイル入出力の基本」を押さえたいとき

には、BufferedReader の直接利用を理解しておくことが重要になります。

「Files を使うか、BufferedReader を使うか」の判断

ざっくりとした目安としては、

ファイルサイズが小さく、すぐに全部 List で欲しい
Files.readAllLines

Stream API で行をフィルタ・変換・集計したい
Files.lines

古いコードを読む、あるいは行ごとの処理を自分でガッツリ書きたい
BufferedReader

という感じです。


try-with-resources とセットで使うのが基本

必ず閉じる必要がある(AutoCloseable)

BufferedReaderCloseable / AutoCloseable を実装しています。

つまり、

  • 使い終わったら close() を呼ばないといけない
  • 閉じ忘れると、ファイルハンドルが開きっぱなしでリソースリークになる

という性質があります。

そのため、現代の Java では、

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    ...
}
Java

のように、try-with-resources とセットで書くのが標準です。

これによって、

  • 正常終了でも
  • 途中で例外が起きても

必ず最後に reader.close() が呼ばれるので、閉じ忘れの心配をしなくて済みます。

NG パターン:close を自分で書き忘れる

例えば、次のようなコードは危険です。

BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line;
while ((line = reader.readLine()) != null) {
    // 処理...
}
// reader.close(); を書き忘れた…
Java

小さなテストプログラムならすぐ終わるので問題が表面化しませんが、
長時間動くサーバーや大量のファイルを扱う処理では、
確実に「リソースリーク」の原因になります。

BufferedReader を見るたびに、「これは try-with-resources で囲むものだ」と条件反射できるようになっておくと安心です。


実務でよくある使い方の例

行ごとのフィルタ処理(例:ERROR 行だけ表示)

ログファイルから「ERROR を含む行だけ」を表示したいとします。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FilterErrorLines {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("app.log"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.contains("ERROR")) {
                    System.out.println(line);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Java

ここでのポイントは、

  • ファイルを開いて
  • 1 行ずつ読みながら条件チェック
  • 条件に合う行だけ出力

という、とても素直な処理を、readLine() で表現できていることです。

設定ファイルを読み込んで Map にする(key=value)

config.txt というファイルに、次のような内容があるとします。

host=localhost
port=8080
mode=dev

これを Map<String, String> に変換する例です。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class ConfigReader {
    public static void main(String[] args) {
        Map<String, String> config = new HashMap<>();

        try (BufferedReader reader = new BufferedReader(new FileReader("config.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (line.isEmpty() || line.startsWith("#")) {
                    continue; // 空行・コメント行をスキップ
                }
                int idx = line.indexOf('=');
                if (idx == -1) continue;

                String key = line.substring(0, idx).trim();
                String value = line.substring(idx + 1).trim();
                config.put(key, value);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("host = " + config.get("host"));
        System.out.println("port = " + config.get("port"));
        System.out.println("mode = " + config.get("mode"));
    }
}
Java

これも、「1 行ずつ読み込む」という BufferedReader の基本を素直に活かしたパターンです。


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

BufferedReader を初心者向けに一言でまとめると、

「他の Reader を包んで、まとめ読み(バッファ)+ readLine() による行単位読みを提供する、テキスト入力用の標準ツール」

です。

意識しておきたいポイントは、

  • 自分自身でファイルを開くのではなく、「何かの Reader を包む」ラッパー
  • 内部バッファのおかげで、1 文字ずつ読むより圧倒的に効率が良い
  • readLine() で「行末の改行を除いた 1 行」を簡単に取得できる
  • ファイルやネットワークなど「閉じるべきリソース」なので、必ず try-with-resources とセットで使う
  • Files.readAllLines / Files.lines などの高レベル API の裏で、似たような役割をしている

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