首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 讀取文字檔完整指南:StreamReader、File 類別、非同步、編碼、大檔案處理

     編輯:關於C語言

前言

讀取文字檔是 C# 開發最基礎也最常見的 I/O 操作。從 .NET Framework 早期的 StreamReader 到現代 .NET 6+ 的 File.ReadAllLinesAsync、IAsyncEnumerable 串流處理,本文系統整理各種讀取方式、適用場景、編碼處理、記憶體效能與錯誤處理最佳實踐。

核心 API 對照表

方法回傳型別同步/非同步記憶體模式適用場景
File.ReadAllTextstring同步全載入記憶體小型設定檔、模板 (< 10MB)
File.ReadAllLinesstring[]同步全載入記憶體逐行處理小檔案
File.ReadLinesIEnumerable<string>同步 (延遲執行)逐行串流大檔案逐行處理、LINQ 查詢
StreamReader.ReadLinestring同步逐行串流需精細控制讀取邏輯
File.ReadAllTextAsyncTask<string>非同步全載入記憶體UI 應用避免凍結
File.ReadLinesAsyncIAsyncEnumerable<string>非同步串流逐行串流.NET 6+ 大檔案非同步處理
StreamReader.ReadLineAsyncTask<string>非同步逐行串流自訂緩衝、管線處理

基本讀取範例

1. 一次讀完整個檔案 (小檔案)

string content = File.ReadAllText("config.json", Encoding.UTF8);
// 或非同步
string content = await File.ReadAllTextAsync("config.json", Encoding.UTF8);

2. 逐行讀取並存入 List (傳統寫法)

var lines = new List();
using (var reader = new StreamReader("data.txt", Encoding.UTF8))
{
    string line;
    while ((line = reader.ReadLine()) != null)
    {
        lines.Add(line);
    }
}

3. 逐行串流處理大檔案 (記憶體友善)

// .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)

4. 非同步串流處理 (.NET 6+ 推薦)

await foreach (string line in File.ReadLinesAsync("huge.log", Encoding.UTF8))
{
    if (line.Contains("ERROR"))
        await ProcessErrorLineAsync(line);
}
// 完全非阻塞,適合高併發服務

5. 管線處理:讀取 → 轉換 → 寫入 (零中間記憶體)

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)。
  • 舊版 .NET Framework 預設為 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);

.NET Core 註冊舊版編碼提供者 (支援 Big5、GB2312、Shift_JIS 等)

// 程式啟動時註冊一次
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
// 需安裝 NuGet: System.Text.Encoding.CodePages

大檔案處理進階技巧

1. 記憶體對應檔案

using var mmf = MemoryMappedFile.CreateFromFile("huge.dat", FileMode.Open);
using var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
// 隨機存取任意位移,適合結構化二進位資料

2. 並行處理大檔案 (PLINQ)

var errorLines = File.ReadLines("huge.log")
    .AsParallel()
    .Where(line => line.Contains("ERROR"))
    .Select(line => ParseError(line))
    .ToList(); // 注意:ToList 會將結果全載入記憶體

3. 斷點續讀 / 斷點續傳上傳

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;
}

效能基準測試 (100MB 文字檔,.NET 7, Release)

方法耗時峰值記憶體備註
File.ReadAllText320 ms~200 MB全載入,GC 壓力大
File.ReadAllLines350 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.ReadLineAsync300 ms~2 MB自訂緩衝可更快

常見問題 FAQ

Q: 讀取 CSV 為什麼中文亂碼?
A: Excel 另存 CSV 預設用系統 ANSI 編碼 (繁中為 Big5)。明確指定 Encoding.GetEncoding("big5") 或要求對方存為 UTF-8。
Q: File.ReadLines 還沒處理完就刪除檔案會怎樣?
A: 會拋出 IOException (檔案不存在)。FileShare.Read 允許其他進程讀取,但不防止刪除。需自行協調檔案生命週期。
Q: 如何跳過前 N 行 (如 CSV 標頭)?
A: File.ReadLines(path).Skip(1).Take(100) 或 reader.ReadLine() 呼叫 N 次後再進入迴圈。
Q: 非同步讀取比同步慢?
A: 小檔案非同步有狀態機器開銷。大檔案/高併發時非同步釋放執行緒,整體吞吐率較高。
Q: 如何處理超長行 (超過 2GB)?
A: StreamReader 內部緩衝區預設 1KB,遇超長行會自動擴充。極端情況改用 FileStream 手動解析換行符。

總結

現代 C# 讀取文字檔的黃金法則:

  1. 小檔案:File.ReadAllTextAsync / File.ReadAllLinesAsync 簡潔易讀。
  2. 大檔案/串流處理:File.ReadLinesAsync (NET 6+) 或 File.ReadLines 逐行迭代,記憶體恆定。
  3. 精細控制/管線:StreamReader + StreamWriter 手動迴圈。
  4. 永遠明確指定 Encoding:Encoding.UTF8 或 Encoding.GetEncoding("big5")。
  5. 善用 using / await using 確保檔案控制代碼及時釋放。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved