CodeGym /課程 /C# SELF /讀取文字檔案: StreamReader...

讀取文字檔案: StreamReader

C# SELF
等級 36 , 課堂 2
開放

1. 前言

想像一下,我們又遇到要讀取文字檔內容的需求。假設有個檔案,裡面存著一堆程式設計師的金句,我們想把它們顯示在我們熟悉的文字應用程式裡。對啦,我們之前已經看過像 File.ReadAllText 這種高階方法,但如果檔案很大,我們不想一次全讀進來,只想一行一行慢慢來?還是你想要有控制地讀檔,比如慢慢載入,避免記憶體爆掉?

這時候 StreamReader 就超好用啦——這個 class 很適合一行一行、區塊、甚至一個字一個字地讀文字檔案,超彈性又方便。

StreamReader 怎麼運作?

StreamReaderSystem.IO 命名空間裡的 class,專門用來從 stream(通常是檔案,也可以是網路 stream 或記憶體)讀取文字資料。它會根據你指定的編碼,把 byte 轉成字元和字串,然後回傳給你超好用的資料型別:string 跟 char。

如果用圖來表示,大概像這樣:


[ 檔案 (bytes) ] --(FileStream)--> [ StreamReader (解析字元編碼) ] ---> [ 你的程式碼 (string, char) ]
  • 檔案(或其他 byte 來源): 給我們 byte。
  • FileStream: 把這些 byte 當作「通道」來讀。
  • StreamReader: 根據編碼把 byte 轉成字元和字串(預設是 UTF-8)。

為什麼不總是只用 File.ReadAllText

實務上,檔案有時會很大,甚至超大(像是 log 或百萬行的 csv)。一次把這種檔案全讀進記憶體,就像一次吃掉整個蛋糕:雖然爽但很危險。
StreamReader 是「分段」讀檔的。它不會把整個檔案丟進記憶體,而是等你要下一行或下一個字時才載入。這樣超省記憶體又很方便。

2. 一行一行讀完整個檔案

我們先做個 quotes.txt 檔案,內容像這樣:


程式碼就是詩。
除錯就是偵探。
「不能動」——最棒的 bug 報告。

現在來加點 code 到我們之前寫的練習小程式(就當它是個慢慢進化的 console app)。把檔案放在 .exe 旁邊,讓程式一行一行把內容印出來。

有註解的程式碼:


// 取得程式資料夾下的檔案路徑
string fileName = "quotes.txt";
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

// 檢查檔案是否存在
if (!File.Exists(filePath))
{
    Console.WriteLine($"檔案找不到: {filePath}");
    return;
}

// 用 StreamReader 開檔來讀
using StreamReader reader = new StreamReader(filePath);
string? line;
int lineNumber = 1;

// 一行一行讀到檔案結尾
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine($"{lineNumber,2}: {line}");
    lineNumber++;
}

這段在幹嘛?

  • 我們用 Path.CombineAppDomain.CurrentDomain.BaseDirectory 算出檔案路徑——這樣 Windows、Linux、甚至 Docker 都能跑。
  • 先檢查檔案真的存在。
  • using 建立 StreamReader,然後用 ReadLine() 一行一行讀。
  • using 區塊結束時,檔案會自動關掉,就算你忘了手動關或遇到例外也沒差。

視覺化:運作流程圖


[ quotes.txt ] --(FileStream)--> [ StreamReader ] --(ReadLine)--> [ string line ] --(Console.WriteLine)--> 螢幕
    ^                                                       
    |
AppDomain.CurrentDomain.BaseDirectory + Path.Combine

3. 使用上的細節與小陷阱

ReadLine 怎麼讀一行

ReadLine() 會回傳到第一個換行字元(\n\r\n)為止的字串。檔案結束時會回傳 null。所以 while 迴圈通常這樣寫:


string? line;
while ((line = reader.ReadLine()) != null)
{
    // 處理這一行
}

用 StreamReader 常見的錯誤

有時候會偷懶不寫 using,直接這樣:


StreamReader reader = new StreamReader(filePath);
// ...
reader.Close();

這種寫法就像冬天忘了關門:什麼事都可能發生。如果讀檔時出錯(比如硬碟突然斷線),關檔的 code 根本不會執行,檔案就一直開著卡在系統裡。
所以 using 不是建議,是必須。你未來的系統管理員同事會感謝你沒讓程式留下「掛著」的檔案。

using declaration 的簡短寫法

C# 8.0 以後可以這樣寫更短:


using StreamReader reader = new StreamReader(filePath);
string? line;
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine(line);
}

Dispose 會在這個區塊(方法)結束時自動呼叫。

4. 實用小技巧

讀未知編碼的檔案

預設 StreamReader 用 UTF-8。但有時候會遇到其他編碼的檔案(像舊的俄文檔案用 Windows-1251)。這時候程式可能會印出「?」或亂碼。

你可以明確指定編碼:


using System.Text;

// 用 Windows-1251 開檔
using StreamReader reader = new StreamReader(filePath, Encoding.GetEncoding("windows-1251"));

編碼與 Encoding 文件

把整個檔案讀成一個大字串

有時候你想一次拿到整個檔案內容,但又不想用 File.ReadAllText,可以用 StreamReader


using StreamReader reader = new StreamReader(filePath);
string allText = reader.ReadToEnd();
Console.WriteLine(allText);
  • ReadToEnd() 會從目前位置把檔案剩下的內容全讀成一個字串。
  • 大檔案這樣做可能會爆記憶體(OutOfMemoryException),但幾 MB 以內都還 OK。

實務上怎麼用

  • 處理 log。 伺服器 log 檔可以一行一行讀,過濾你要的事件,不用全丟進記憶體。
  • 匯入 CSV。 解析大表格時,一行一行讀進來處理就好。
  • 在大檔案裡找關鍵字。 你可以在百萬行裡找特定字串,不用怕記憶體爆掉。
  • 單元測試。 測試資料檔常常用 StreamReader 一行一行讀。

各種讀檔方法比較

方法 什麼時候用 優點 缺點
File.ReadAllText
小檔案 一行 code,超快 大檔案會吃爆記憶體
StreamReader.ReadLine()
任何檔案,尤其大檔 一行一行讀,很省記憶體 code 稍微多一點,邏輯要自己寫
StreamReader.ReadToEnd()
小/中型檔案 可以自己控制編碼 超大檔案會很痛苦

5. 練習

來點進階的。讓程式只印出前面 N 行,N 由使用者輸入。有時候你不想一次看到幾百行,幾句金句就夠激勵人心了 :)

這樣寫就對了:


using System;
using System.IO;

class Program
{
    static void Main()
    {
        string fileName = "quotes.txt";
        string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

        if (!File.Exists(filePath))
        {
            Console.WriteLine($"檔案找不到: {filePath}");
            return;
        }

        Console.Write("要印出幾行?");
        string? input = Console.ReadLine();
        if (!int.TryParse(input, out int linesToShow) || linesToShow < 1)
        {
            Console.WriteLine("錯誤:請輸入大於 0 的正確數字。");
            return;
        }

        using StreamReader reader = new StreamReader(filePath);

        int current = 0;
        string? line;
        while (current < linesToShow && (line = reader.ReadLine()) != null)
        {
            Console.WriteLine(line);
            current++;
        }

        if (current == 0)
            Console.WriteLine("檔案是空的或第一行讀不到。");
        else if (current < linesToShow)
            Console.WriteLine($"檔案只有 {current} 行。");
    }
}

6. 重要提醒與常見陷阱

StreamReader 時,最重要的就是記得正確管理資源(參考前一堂 IDisposable 的課)。不然你可能會遇到 「檔案已被其他程序使用」「開太多檔案」 這種錯誤。

還有一點要注意——編碼。如果你不確定檔案是不是 UTF-8,記得明確指定編碼。StreamReader 的建構子有第二個參數可以用。

還有一個實務小陷阱:如果檔案在程式運行時會被更新(像 log 檔),你不一定馬上看得到新的一行。如果檔案被其他程序用著,可能要重算/重讀檔案,或用特殊方法,這部分以後再聊。

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