1. 前言
想像一下,我們又遇到要讀取文字檔內容的需求。假設有個檔案,裡面存著一堆程式設計師的金句,我們想把它們顯示在我們熟悉的文字應用程式裡。對啦,我們之前已經看過像 File.ReadAllText 這種高階方法,但如果檔案很大,我們不想一次全讀進來,只想一行一行慢慢來?還是你想要有控制地讀檔,比如慢慢載入,避免記憶體爆掉?
這時候 StreamReader 就超好用啦——這個 class 很適合一行一行、區塊、甚至一個字一個字地讀文字檔案,超彈性又方便。
StreamReader 怎麼運作?
StreamReader 是 System.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.Combine 跟 AppDomain.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"));
把整個檔案讀成一個大字串
有時候你想一次拿到整個檔案內容,但又不想用 File.ReadAllText,可以用 StreamReader。
using StreamReader reader = new StreamReader(filePath);
string allText = reader.ReadToEnd();
Console.WriteLine(allText);
- ReadToEnd() 會從目前位置把檔案剩下的內容全讀成一個字串。
- 大檔案這樣做可能會爆記憶體(OutOfMemoryException),但幾 MB 以內都還 OK。
實務上怎麼用
- 處理 log。 伺服器 log 檔可以一行一行讀,過濾你要的事件,不用全丟進記憶體。
- 匯入 CSV。 解析大表格時,一行一行讀進來處理就好。
- 在大檔案裡找關鍵字。 你可以在百萬行裡找特定字串,不用怕記憶體爆掉。
- 單元測試。 測試資料檔常常用 StreamReader 一行一行讀。
各種讀檔方法比較
| 方法 | 什麼時候用 | 優點 | 缺點 |
|---|---|---|---|
|
小檔案 | 一行 code,超快 | 大檔案會吃爆記憶體 |
|
任何檔案,尤其大檔 | 一行一行讀,很省記憶體 | code 稍微多一點,邏輯要自己寫 |
|
小/中型檔案 | 可以自己控制編碼 | 超大檔案會很痛苦 |
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 檔),你不一定馬上看得到新的一行。如果檔案被其他程序用著,可能要重算/重讀檔案,或用特殊方法,這部分以後再聊。
GO TO FULL VERSION