CodeGym /課程 /JAVA 25 SELF /讀取文字檔:逐行與整檔

讀取文字檔:逐行與整檔

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

1. 逐行讀取檔案:BufferedReader 與同類工具

先前我們已經看過逐位元組讀取:它適合處理二進位格式,但對文字就不太方便。檔案由取決於編碼的字元組成。因此 Java 提供了便利的高階封裝——FileReaderBufferedReader 等類別,能把位元組串流轉成字元與文字行。

想像一個文字檔——可能是程式的運作日誌、使用者清單,甚至是龐大的長篇小說《戰爭與和平》。有時你需要快速把整個檔案讀進來,有時則要逐行走訪,還有時候只想抽出特定的一行。

在 Java 中有多種做法可達成,選擇取決於檔案大小與手頭任務。若需要逐行處理(例如計算行數或尋找某筆紀錄),就用逐行讀取;若檔案不大,也可以整檔載入記憶體,當作行的清單來操作。

為什麼要逐行讀取?

對大型檔案而言,逐行讀取能避免記憶體問題。把數 GB 的日誌整個載入記憶體不是好主意:很可能會遇到 OutOfMemoryError。而逐行讀取即使面對數百 MB 的檔案也能輕鬆應付。

在 Java 中怎麼做?

最經典的方法是使用 BufferedReader(或其近親)以及 readLine() 方法。

import java.io.*;

public class ReadLinesDemo {
    public static void main(String[] args) {
        String fileName = "example.txt";

        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            int lineNumber = 1;
            while ((line = reader.readLine()) != null) {
                System.out.printf("%3d: %s%n", lineNumber, line);
                lineNumber++;
            }
        } catch (IOException e) {
            System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
        }
    }
}

在這裡我們開啟檔案來讀取,並透過 readLine() 一次讀一行,直到回傳 null 表示到達檔尾。接著將每一行連同行號輸出。

關於 try-with-resources 的重點

你看到的結構 try (...) { ... } 就是 try-with-resources。它保證即使在讀取過程中發生錯誤,檔案也會被關閉。你不需要手動呼叫 close():即使進入 catch/finally,也會自動關閉。

為什麼需要 BufferedReader?

BufferedReader 並非逐字元讀取,而是一次讀取整個區塊(通常為 8192 位元組),這能加速檔案操作。此外,它提供便利的 readLine() 方法,可直接回傳到換行字元為止的一整行。

緩衝區該選多大?

一般而言,BufferedReader 使用大小為 8192 位元組(8 KB)的緩衝區——對多數情境已綽綽有餘。若你會讀到非常長的行(例如每行 100_000 個字元),可以把緩衝區加大:

BufferedReader reader = new BufferedReader(new FileReader(fileName), 65536); // 64 KB 緩衝區

但對一般需求,預設大小已經很適合。

2. 讀取整個檔案:Files.readAllLines 與 Files.readString

如果檔案不大(例如約 1020 MB),把它整個載入會很方便。比如要快速取得所有行、分析文字,或透過網路傳送。

較新的作法: Files.readAllLines

從 Java 7 起有了好用的 Files 類別,裡面包含許多實用方法。

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class ReadAllLinesDemo {
    public static void main(String[] args) {
        Path path = Path.of("example.txt");

        try {
            List<String> lines = Files.readAllLines(path);
            for (int i = 0; i < lines.size(); i++) {
                System.out.printf("%3d: %s%n", i + 1, lines.get(i));
            }
        } catch (IOException e) {
            System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
        }
    }
}

這裡 Files.readAllLines(path) 會回傳行的清單(List<String>)。你可以像操作一般集合一樣:搜尋、排序、篩選。

更進一步: Files.readString (Java 11+)

如果你需要把整個檔案一次取得為單一字串(例如用來搜尋子字串或傳成 JSON),使用 Files.readString

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

public class ReadStringDemo {
    public static void main(String[] args) {
        Path path = Path.of("example.txt");

        try {
            String content = Files.readString(path);
            System.out.println("檔案內容:");
            System.out.println(content);
        } catch (IOException e) {
            System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
        }
    }
}

那編碼怎麼辦?

預設情況下,Files.readAllLinesFiles.readString 會使用系統的預設編碼。若檔案使用了其他編碼(例如 Windows-1251),請明確指定:

List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
String content = Files.readString(path, StandardCharsets.UTF_8);

3. 方法比較: 何時用哪一種

方法 適用時機 優點 缺點
BufferedReader.readLine()
大型檔案、逐行處理 省記憶體、彈性高 只能逐行讀取
Files.readAllLines()
小到中型檔案 直接得到行清單,簡單 對大型檔案可能導致 OutOfMemory
Files.readString()
小型檔案,需要整段文字 一次取得全部內容 不會自動依行分割

建議:
— 若檔案不大,使用 Files.readAllLinesFiles.readString
— 若檔案很大或你不確定大小,使用 BufferedReader.readLine()

4. 實務任務: 範例與解析

範例 1. 大型檔案的行數統計

假設需要知道一個超大的檔案(例如伺服器日誌)共有多少行。

import java.io.*;

public class LineCount {
    public static void main(String[] args) {
        String fileName = "biglog.txt";
        int lineCount = 0;

        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            while (reader.readLine() != null) {
                lineCount++;
            }
            System.out.println("檔案中的總行數:" + lineCount);
        } catch (IOException e) {
            System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
        }
    }
}

範例 2. 依內容搜尋行

尋找所有出現「錯誤」一詞的行(不分大小寫):

import java.io.*;

public class FindErrorLines {
    public static void main(String[] args) {
        String fileName = "biglog.txt";
        String keyword = "錯誤";

        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            int lineNumber = 1;
            while ((line = reader.readLine()) != null) {
                if (line.toLowerCase().contains(keyword)) {
                    System.out.printf("%3d: %s%n", lineNumber, line);
                }
                lineNumber++;
            }
        } catch (IOException e) {
            System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
        }
    }
}

範例 3. 從小型檔案載入設定

檔案 config.txt

host=localhost
port=8080
mode=dev

整檔讀取並解析為鍵值配對:

import java.nio.file.*;
import java.util.*;

public class ConfigLoader {
    public static void main(String[] args) throws Exception {
        Path path = Path.of("config.txt");
        List<String> lines = Files.readAllLines(path);

        Map<String, String> config = new HashMap<>();
        for (String line : lines) {
            if (line.trim().isEmpty() || line.startsWith("#")) continue; // 略過空行與註解
            String[] parts = line.split("=", 2);
            if (parts.length == 2) {
                config.put(parts[0].trim(), parts[1].trim());
            }
        }

        System.out.println("已載入的設定:" + config);
    }
}

5. 讀取文字檔的常見錯誤

錯誤 №1: 把二進位檔當成文字檔來讀。 如果你用 BufferedReaderFiles.readAllLines 來開影像或壓縮檔,只會得到亂碼,還存在 OutOfMemoryError 的風險。處理二進位檔請使用 InputStream

錯誤 №2: 未處理例外。 檔案可能會被刪除、搬移、鎖定。務必用 try-catch 包住讀取並告知使用者問題。

錯誤 №3: 忽略編碼。 如果你的文字是俄文,而你未指定編碼,可能會看到「?????」。請使用 StandardCharsets.UTF_8 或你需要的編碼。

錯誤 №4: 忘了關閉串流。 若不關閉檔案,它可能會一直被鎖到程式結束。務必使用 try-with-resources。

錯誤 №5: 對文字檔用 read() 代替 readLine()。 方法 read() 是逐字元讀取——對處理文字行既慢又不便。讀文字請用 readLine()Files 類別的方法。

1
任務
JAVA 25 SELF, 等級 36, 課堂 1
上鎖
合併偉大史詩的片段 📖
合併偉大史詩的片段 📖
1
任務
JAVA 25 SELF, 等級 36, 課堂 1
上鎖
尋找間諜檔案中的共同機密 🕵️
尋找間諜檔案中的共同機密 🕵️
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION