讀取文字檔是 C# 開發最基礎也最常見的 I/O 操作。從 .NET Framework 早期的 StreamReader 到現代 .NET 6+ 的 File.ReadAllLinesAsync、IAsyncEnumerable 串流處理,本文系統整理各種讀取方式、適用場景、編碼處理、記憶體效能與錯誤處理最佳實踐。
| 方法 | 回傳型別 | 同步/非同步 | 記憶體模式 | 適用場景 |
|---|---|---|---|---|
File.ReadAllText | string | 同步 | 全載入記憶體 | 小型設定檔、模板 (< 10MB) |
File.ReadAllLines | string[] | 同步 | 全載入記憶體 | 逐行處理小檔案 |
File.ReadLines | IEnumerable<string> | 同步 (延遲執行) | 逐行串流 | 大檔案逐行處理、LINQ 查詢 |
StreamReader.ReadLine | string | 同步 | 逐行串流 | 需精細控制讀取邏輯 |
File.ReadAllTextAsync | Task<string> | 非同步 | 全載入記憶體 | UI 應用避免凍結 |
File.ReadLinesAsync | IAsyncEnumerable<string> | 非同步串流 | 逐行串流 | .NET 6+ 大檔案非同步處理 |
StreamReader.ReadLineAsync | Task<string> | 非同步 | 逐行串流 | 自訂緩衝、管線處理 |
string content = File.ReadAllText("config.json", Encoding.UTF8);
// 或非同步
string content = await File.ReadAllTextAsync("config.json", Encoding.UTF8);
var lines = new List();
using (var reader = new StreamReader("data.txt", Encoding.UTF8))
{
string line;
while ((line = reader.ReadLine()) != null)
{
lines.Add(line);
}
}
// .NET Core 2.1+ / .NET Standard 2.1+
foreach (string line in File.ReadLines("huge.log", Encoding.UTF8))
{
if (line.Contains("ERROR"))
ProcessErrorLine(line);
}
// 記憶體佔用恆定約緩衝區大小 (預設 1KB)
await foreach (string line in File.ReadLinesAsync("huge.log", Encoding.UTF8))
{
if (line.Contains("ERROR"))
await ProcessErrorLineAsync(line);
}
// 完全非阻塞,適合高併發服務
using var input = File.OpenRead("input.csv");
using var reader = new StreamReader(input, Encoding.UTF8);
using var output = File.CreateText("output.csv");
using var writer = new StreamWriter(output, Encoding.UTF8);
await writer.WriteLineAsync("Id,Name,UpperName");
while (await reader.ReadLineAsync() is { } line)
{
var parts = line.Split(',');
if (parts.Length >= 2)
{
await writer.WriteLineAsync($"{parts[0]},{parts[1]},{parts[1].ToUpper()}");
}
}
StreamReader / File.ReadAllText 預設使用 Encoding.UTF8 (無 BOM)。Encoding.Default (系統 ANSI 頁碼頁,如 Big5/GBK),跨平台易亂碼。// 讀取 Big5 編碼的舊系統匯出檔
using var reader = new StreamReader("legacy.txt", Encoding.GetEncoding("big5"));
// 偵測 BOM 自動判斷編碼
using var reader = new StreamReader("file.txt", detectEncodingFromByteOrderMarks: true);
// 程式啟動時註冊一次
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
// 需安裝 NuGet: System.Text.Encoding.CodePages
using var mmf = MemoryMappedFile.CreateFromFile("huge.dat", FileMode.Open);
using var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
// 隨機存取任意位移,適合結構化二進位資料
var errorLines = File.ReadLines("huge.log")
.AsParallel()
.Where(line => line.Contains("ERROR"))
.Select(line => ParseError(line))
.ToList(); // 注意:ToList 會將結果全載入記憶體
long lastPosition = GetLastReadPosition(); // 從資料庫/檔案讀取上次位移
using var fs = new FileStream("huge.log", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
fs.Seek(lastPosition, SeekOrigin.Begin);
using var reader = new StreamReader(fs, Encoding.UTF8);
string line;
while ((line = reader.ReadLine()) != null)
{
Process(line);
SavePosition(fs.Position); // 定期儲存位移
}
public async Task> ReadLinesSafeAsync(string path, Encoding encoding, CancellationToken ct = default)
{
if (!File.Exists(path))
throw new FileNotFoundException($"找不到檔案: {path}");
var lines = new List();
try
{
await foreach (var line in File.ReadLinesAsync(path, encoding, ct))
{
lines.Add(line);
}
}
catch (UnauthorizedAccessException)
{
throw new IOException($"無權限讀取: {path}");
}
catch (IOException ex) when (ex.HResult == -2147024864) // ERROR_SHARING_VIOLATION
{
throw new IOException($"檔案被鎖定: {path}", ex);
}
return lines;
}
| 方法 | 耗時 | 峰值記憶體 | 備註 |
|---|---|---|---|
| File.ReadAllText | 320 ms | ~200 MB | 全載入,GC 壓力大 |
| File.ReadAllLines | 350 ms | ~220 MB | 字串陣列額外開銷 |
| File.ReadLines (foreach) | 280 ms | ~2 MB | 串流,記憶體恆定 |
| StreamReader.ReadLine (while) | 270 ms | ~2 MB | 手動控制,最快 |
| File.ReadLinesAsync (await foreach) | 310 ms | ~3 MB | 非同步開銷微小 |
| StreamReader.ReadLineAsync | 300 ms | ~2 MB | 自訂緩衝可更快 |
Encoding.GetEncoding("big5") 或要求對方存為 UTF-8。File.ReadLines 還沒處理完就刪除檔案會怎樣?IOException (檔案不存在)。FileShare.Read 允許其他進程讀取,但不防止刪除。需自行協調檔案生命週期。File.ReadLines(path).Skip(1).Take(100) 或 reader.ReadLine() 呼叫 N 次後再進入迴圈。StreamReader 內部緩衝區預設 1KB,遇超長行會自動擴充。極端情況改用 FileStream 手動解析換行符。現代 C# 讀取文字檔的黃金法則:
File.ReadAllTextAsync / File.ReadAllLinesAsync 簡潔易讀。File.ReadLinesAsync (NET 6+) 或 File.ReadLines 逐行迭代,記憶體恆定。StreamReader + StreamWriter 手動迴圈。Encoding.UTF8 或 Encoding.GetEncoding("big5")。