CSV 格式看似簡單,實則包含引號包裹、內嵌逗號、雙引號跳脫 ("" 代表 ")、跨行欄位等邊界情況。正則表達式難以全面覆蓋且效能不可控,有限狀態機 (FSM) 以 O(n) 時間、O(1) 額外空間單趟掃描,是工業級解析器的標準實作。
Normal — 一般字元累積進當前欄位,遇逗號輸出欄位,遇雙引號進入 InQuote。InQuote — 引號內字元原樣累積 (含逗號、換行),遇雙引號:若下一字元也是雙引號則寫入一個雙引號並跳過;否則結束引號區塊回 Normal。using System;
using System.Collections.Generic;
using System.Text;
public static class CsvParser
{
public static List ParseLine(string line)
{
var result = new List();
var current = new StringBuilder();
bool inQuote = false;
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c == '"')
{
if (inQuote)
{
// 可能是跳脫雙引號 或 結束引號
if (i + 1 < line.Length && line[i + 1] == '"')
{
current.Append('"'); // 寫入單個雙引號
i++; // 跳過下一個雙引號
}
else
{
inQuote = false; // 結束引號區塊
}
}
else
{
inQuote = true; // 開始引號區塊
}
}
else if (c == ',' && !inQuote)
{
// 欄位分隔
result.Add(current.ToString());
current.Clear();
}
else
{
current.Append(c);
}
}
result.Add(current.ToString()); // 最後一欄
return result;
}
}
// 測試
var line = "1,2,3,\"4,5\",6,\"7\"\"8\"";
foreach (var f in CsvParser.ParseLine(line))
Console.WriteLine($"[{f}]");
// 輸出: [1] [2] [3] [4,5] [6] [7"8]
TextReader 逐字讀取,狀態機邏輯不變,自然支援跨行欄位。\r\n 行尾、空欄位、首尾空白保留選項。Span 或預分配陣列,避免 StringBuilder 擴容開銷。狀態機是解析上下文相關文法 (如 CSV、JSON、XML 子集) 的利器。掌握「狀態 + 輸入字元 → 動作 + 下一狀態」的建模思維,即可手寫高效、零依賴、全邊界覆蓋的解析器。