字串操作:模式匹配、效能與基於張距的搜尋

本文涵蓋三種字串操作:正則表達式模式匹配, System.Text.RegularExpressions.Regex對 ,無分配搜尋, ReadOnlySpan<T>以及選擇 StringComparison 一個值以進行正確且快速的比較。

透過正則表達式尋找特定文本

System.Text.RegularExpressions.Regex 類別搜尋字串以尋找模式,而非固定子字串。 靜態 Regex.IsMatch 方法則會接收輸入字串、一個模式以及可選 RegexOptions 的旗標。

以下範例會在每句中搜尋 thetheir,且不分格。 模式 the(ir)?\s 匹配 the ,後接 ir,接著空白字元:

樣式 Meaning
the 與字面文字相符 the
(ir)? 匹配 0 或 1 的出現 ir
\s 匹配空白字元
string[] sentences =
[
    "Put the water over there.",
    "They're quite thirsty.",
    "Their water bottles broke."
];

string pattern = @"the(ir)?\s";

foreach (string s in sentences)
{
    Console.Write($"{s,28}");

    if (Regex.IsMatch(s, pattern, RegexOptions.IgnoreCase))
    {
        Console.WriteLine($"  (match for '{pattern}' found)");
    }
    else
    {
        Console.WriteLine();
    }
}

驗證字串是否符合模式

要檢查整個輸入是否符合形狀,請用 ^$錨定該圖案。 以下範例驗證每個字串為美式電話號碼:三位數、三位數、四位數,並以破折號分隔:

樣式 Meaning
^ 與字串的開頭相符
\d{3} 完全匹配三位數字元
- 匹配字面 - 上的字面角色
\d{4} 完全匹配四個位數字元
$ 匹配繩尾
string[] numbers =
[
    "123-555-0190",
    "444-234-22450",
    "690-555-0178",
    "146-893-232",
    "146-555-0122",
    "4007-555-0111",
    "407-555-0111",
    "407-2-5555",
    "407-555-8974",
    "407-2ab-5555",
    "690-555-8148",
    "146-893-232-"
];

string pattern = """^\d{3}-\d{3}-\d{4}$""";

foreach (string s in numbers)
{
    Console.Write($"{s,14}");
    Console.WriteLine(Regex.IsMatch(s, pattern) ? " - valid" : " - invalid");
}

完整模式語法請參見 正則表達式語言 - 快速參考

請在方法與正則表達式之間 string 選擇

string 方法並 Regex 解決重疊問題。 當你搜尋的文字是字面值、已知的前綴或後綴,或固定分隔符時,建議 string 使用方法。 它們更簡單且更快,因為不需要支付編譯和執行模式的成本。 當搜尋目標為圖形時,請選擇Regex搜尋,例如交替、可選群組、重複字元類別或錨定驗證。 一般來說,如果你能把搜尋寫成一到兩次string.Contains / / StartsWithIndexOf通話,就這麼做。

搜尋 ReadOnlySpan<char>

當你解析大型輸入或在熱路徑上搜尋時,和string.Split的每次通話分配string.Substring可能會佔據主導地位。 ReadOnlySpan<char> 提供您對現有字串(或陣列、堆疊緩衝區)的檢視,無需複製,並提供 MemoryExtensions 基於 string span 的常見方法等價功能,包括 IndexOf

ReadOnlySpan<char> input = "key1=alpha;key2=beta;key3=gamma".AsSpan();
ReadOnlySpan<char> needle = "key2=".AsSpan();

int start = input.IndexOf(needle);
if (start >= 0)
{
    ReadOnlySpan<char> rest = input[(start + needle.Length)..];
    int end = rest.IndexOf(';');
    ReadOnlySpan<char> value = end >= 0 ? rest[..end] : rest;
    Console.WriteLine($"key2 = {value}");
}
// => key2 = beta

基於張寬的搜尋避免了分配,因為切片(input[start..]rest[..end])只是原始字元上的視窗。 同樣的方法也適用於解析鍵值清單、標頭及其他分隔文字,而無需呼叫 Substring

StringComparison 的效能考量事項

大多數 string 實例方法都有過載,可以接受一個 StringComparison 值。 String.Equals(String)例如預設為序數,但String.Compare(String, String)String.IndexOf(String)預設為當前文化。 這個差異有兩個重要方面:

  • 速度。 序數比較是一種位元組對位元組的測試,以緊密且向量化的迴圈進行。 文化感知比較會參考排序表、行走組合角色,並套用特定地區規則。 對於相同的輸入,速度可能會慢一個數量級。
  • 正確性。 文化意識的比較可能會 i/I讓你意外出現(土耳其人、德國 ß 人、 ss連字)。 這種行為對於排序使用者看到的名稱是正確的,但在解析識別碼、路徑或協定標記時則錯誤。

對於機器定義的文字,例如檔名、URL、HTTP 標頭、識別碼和設定鍵,分別是傳遞 StringComparison.OrdinalStringComparison.OrdinalIgnoreCase 明確。 為顯示給使用者的自然語言文字保留文化意識值。 如需完整指引,請參閱 比較 .NET

另請參閱