CodeGym /課程 /JAVA 25 SELF /BufferedReader、BufferedWriter:緩衝與優點

BufferedReader、BufferedWriter:緩衝與優點

JAVA 25 SELF
等級 36 , 課堂 0
開放

1. 前言

回顧一下 FileReaderFileWriter 的工作方式。每次你呼叫它們的 read()write() 方法,都會進行一次對檔案系統的存取——也就是電腦真的去磁碟上讀或寫一個字元。如果檔案很大,而你一次只讀或寫一個字元,這就會變成成千上萬甚至數百萬次的磁碟存取「馬拉松」。而磁碟,眾所周知,並不是程式設計師最快的朋友。

可以這樣想像:你要把水從水桶倒進瓶子,卻用茶匙來舀。形式上能做,但會非常久。更明智的做法是用杓子或杯子。對檔案也是一樣:一次只讀或寫一個字元,就像用茶匙搬水。

實際上會長這樣:

// 逐字元讀取檔案(用「茶匙」!)
try (FileReader reader = new FileReader("big.txt")) {
    int c;
    while ((c = reader.read()) != -1) {
        // 處理字元(例如,單純計數)
    }
}

如果檔案很大,你會注意到程式執行得非常緩慢。

緩衝:是什麼、為什麼需要

緩衝(buffer)是一塊特殊的記憶體區域(通常是陣列),用來一次載入或寫入較大的資料塊(例如 8 KB 或更多),而不是一個字元一個字元處理。簡單說,緩衝就是一塊記憶體,類似中間的水桶,資料會先被成批讀入或寫入。

運作方式是:在讀取時,程式只需向磁碟請求一次,就能把整個資料區塊載入緩衝,之後再從記憶體中一個字元一個字元地把資料「分發」給你。一旦該區塊用完,就再載入下一個。寫入時也一樣:資料會先累積到緩衝區,接著再一次性寫到磁碟(或若你呼叫了 flush(),就會立即寫出)。

為什麼更快?因為磁碟本身很慢,特別是被頻繁「零碎地」存取時。而若是減少存取次數、一次處理更大的資料量,效能就會明顯提升。更白話地說:緩衝能減少對磁碟的存取次數,並讓程式執行更快。

2. BufferedReader 與 BufferedWriter:語法與範例

在 Java 中,讀寫文字檔案的緩衝化由兩個類別負責:

  • BufferedReader —— 用於讀取文字檔案。
  • BufferedWriter —— 用於寫入文字檔案。

它們包裹在一般的 Reader/Writer(例如 FileReader/FileWriter)之上,提供緩衝功能。

使用 BufferedReader 逐行讀取檔案

最常見的情境是逐行讀取。readLine() 方法會傳回從目前位置到換行符號為止的一行("\n""\r\n")。

import java.io.*;

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line); // 將該行輸出到螢幕
            }
        } catch (IOException e) {
            System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
        }
    }
}

這裡發生了什麼?

我們建立能逐字元讀檔的 FileReader,並把它包在 BufferedReader 中。緩衝讀取器不是一次取一個字元,而是一次抓取較大的資料塊到記憶體,然後透過 readLine() 逐行提供給你。結果是你只要寫個 while 迴圈就能一行一行取得字串,不用擔心檔案多大:讀取依然快速且省資源。

使用 BufferedWriter 寫入檔案

寫入字串到檔案也可以很有效率:

import java.io.*;

public class BufferedWriterExample {
    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            writer.write("Hello, world!");
            writer.newLine(); // 換行(取決於作業系統)
            writer.write("這是第二行。");
        } catch (IOException e) {
            System.out.println("寫入檔案時發生錯誤:" + e.getMessage());
        }
    }
}

這裡發生了什麼?

先建立 FileWriter,再把它包在 BufferedWriter 中。當你呼叫 write()newLine() 時,資料不會立刻寫到磁碟,而是先進入緩衝——一塊中介的記憶體區。等到緩衝區滿了,或你關閉串流(或顯式呼叫 flush())時,累積的文字才會一次性寫入檔案。這樣能顯著加速寫入並減少對磁碟的存取。

在一個完整應用程式中會是什麼樣子?

假設我們寫一個簡單的「日記」程式,把條目存到檔案並顯示在螢幕上。

import java.io.*;
import java.util.Scanner;

public class DiaryApp {
    public static void main(String[] args) {
        String fileName = "diary.txt";
        Scanner scanner = new Scanner(System.in);

        // 新增一則條目
        System.out.print("請輸入新的日記內容:");
        String entry = scanner.nextLine();

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName, true))) {
            writer.write(entry);
            writer.newLine();
            System.out.println("記錄已儲存!");
        } catch (IOException e) {
            System.out.println("寫入時發生錯誤:" + e.getMessage());
        }

        // 讀取所有條目
        System.out.println("\n您的日記:");
        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("讀取時發生錯誤:" + e.getMessage());
        }
    }
}

我們拿到檔名 "diary.txt",並請使用者輸入新的條目。為了儲存,我們用 FileWriterappend 模式(傳入 true),因此舊的內容不會被覆蓋——每一行都會被追加到檔案末尾。BufferedWriter 這層包裝讓寫入更快且省資源:資料先累積在記憶體,再一次寫入磁碟。

接著我們用 BufferedReader 開啟同一個檔案來讀取。它以較大的區塊讀取內容,再逐行提供給你。在迴圈中程式只負責把每行輸出到螢幕,最後你就能從頭到尾看到整本「日記」。

3. BufferedReader 與 BufferedWriter 的優點

顯著的效能提升

使用緩衝化的讀寫時,程式對磁碟的存取次數會少上好幾十倍、甚至上百倍。這在大檔案上特別明顯。

方便的方法

  • BufferedReader.readLine() —— 允許逐行讀取,對處理文字檔(如日誌、CSV、設定檔)非常方便。
  • BufferedWriter.newLine() —— 會依作業系統自動使用正確的換行符號。

易於使用

  • 類別能輕鬆與其他串流組合(例如把 InputStreamReader 包在 BufferedReader 裡以支援不同編碼)。
  • 搭配 try-with-resources 使用時會自動關閉所有資源。

彈性

  • 可以明確指定緩衝區大小,如果預設值(通常 8 KB)不合適:
BufferedReader reader = new BufferedReader(new FileReader("big.txt"), 16384); // 16 KB 的緩衝區

4. 何時使用 BufferedReader 與 BufferedWriter

適用於:

  • 處理文字檔案(如日誌、CSV、大量文字資料)。
  • 需要逐行讀寫檔案的情況。
  • 在意效能、需要加速處理大型檔案時。
  • 需要處理來自網路或其他來源、且支援 Reader/Writer 的資料流。

不適用於:

  • 處理二進位檔案(例如圖片、壓縮檔、影片)——請改用 InputStream/OutputStream
  • 檔案非常小,且只需一次性完整讀/寫——緩衝帶來的效益極小(但通常也不會有壞處)。

5. 實用細節

與不同編碼搭配使用

如果你需要用特定編碼讀/寫檔案(例如 "UTF-8""Windows-1251"),請把 InputStreamReader/OutputStreamWriter 與緩衝化串流搭配使用:

BufferedReader reader = new BufferedReader(
    new InputStreamReader(new FileInputStream("input.txt"), "UTF-8")
);

BufferedWriter writer = new BufferedWriter(
    new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8")
);

顯式沖刷緩衝區

有時候你需要確保資料真的已經寫到磁碟(例如寫日誌或發票)。此時請呼叫 writer.flush()。通常不需要這麼做,因為關閉串流會自動沖刷緩衝區。

緩衝區大小

預設緩衝區大小約為 8 KB。若你確定更改大小能帶來效能提升(例如處理巨型檔案),就可以自行調整。

比較:FileReader/FileWriter vs BufferedReader/BufferedWriter

類別 在大型檔案上的速度 逐行讀取的便利性 逐行寫入的便利性 與編碼相關的彈性
FileReader/FileWriter 否(只能逐字元) 否(只能逐字元) 僅預設
BufferedReader/Writer 是(readLine() 是(newLine() 是(透過 InputStreamReader/OutputStreamWriter

6. 使用 BufferedReader 與 BufferedWriter 的常見錯誤

錯誤 №1:忘記關閉串流。 若不使用 try-with-resources 或沒呼叫 close(),檔案可能會保持鎖定,資料也可能不會寫入磁碟。請務必使用 try-with-resources

錯誤 №2:混用文字與二進位檔案的處理方式。 嘗試用 BufferedReader 開啟二進位檔(".jpg"".zip")會產生亂碼,甚至導致錯誤。處理二進位檔請使用 InputStream/OutputStream

錯誤 №3:在大量資料處理時不使用緩衝。 若逐字元讀寫,程式會很慢。處理大檔案時請務必使用緩衝。

錯誤 №4:需要時沒有呼叫 flush() 當你需要資料立刻寫到磁碟(例如記錄日誌),請呼叫 writer.flush()。但通常關閉串流就會自動處理。

錯誤 №5:忽略編碼。 若以錯誤的編碼開啟檔案,文字可能會顯示不正確(例如字母變成「?」、亂碼等)。當檔案編碼不同於系統預設時,請務必明確指定所需的編碼(例如 "UTF-8")。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION