文字列操作: パターン マッチング、パフォーマンス、スパンベースの検索

この記事では、 System.Text.RegularExpressions.Regexを使用した正規表現パターン マッチング、 ReadOnlySpan<T>に対する割り当て不要の検索、および正しい高速比較のための StringComparison 値の選択という 3 つの文字列操作について説明します。

正規表現を使用して特定のテキストを検索する

System.Text.RegularExpressions.Regex クラスは、固定部分文字列ではなくパターンを文字列で検索します。 静的 Regex.IsMatch メソッドは、入力文字列、パターン、および省略可能な RegexOptions フラグを受け取ります。

次の例では、各文で、大文字と小文字区別されない単語を検索します。 パターン the(ir)?\s は、必要に応じて the に続けて ir、次に空白文字と一致します。

Pattern 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();
    }
}

パターンに対して文字列を検証する

入力全体が図形と一致するかどうかを確認するには、パターンを ^ して $に固定します。 次の例では、各文字列が米国スタイルの電話番号であることを検証します。3 桁、3 桁、4 桁、ダッシュで区切ります。

Pattern Meaning
^ 文字列の先頭と一致する
\d{3} 完全に 3 桁の文字に一致する
- リテラル - 文字と一致する
\d{4} 完全に 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に到達します。 経験則として、検索を 1 つまたは 2 つの string.Contains / StartsWith / IndexOf 呼び出しとして記述できる場合は、これを行います。

を使用して検索する ReadOnlySpan<char>

大規模な入力を解析したり、ホット パスで検索を実行したりすると、 string.Substringstring.Split の呼び出しごとの割り当てが優勢になる可能性があります。 ReadOnlySpan<char>では、コピーせずに既存の文字列 (または配列、またはスタック バッファー) を表示でき、MemoryExtensionsでは、IndexOfを含む一般的なstring メソッドと同等のスパンベースのメソッドが提供されます。

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)既定で現在のカルチャに設定されます。 この違いは、次の 2 つの方法で重要です。

  • 速度。 序数比較は、厳密なベクター化されたループで実行されるバイト単位のテストです。 カルチャに対応した比較では、並べ替えテーブルを参照し、文字を組み合わせて、ロケール固有の規則を適用します。 同じ入力の場合、1 桁遅くなる可能性があります。
  • 正確さ。 カルチャに対応した比較では、予期しない文字を折りたたむことができます (トルコ語 i/I、ドイツ語の ßss、合字)。 この動作は、ユーザーに表示される名前を並べ替えるのに適していますが、識別子、パス、またはプロトコル トークンの解析には間違っています。

ファイル名、URL、HTTP ヘッダー、識別子、構成キーなどのコンピューター定義テキストの場合は、 StringComparison.Ordinal または StringComparison.OrdinalIgnoreCase 明示的に渡します。 ユーザーに表示される自然言語テキストのカルチャに対応した値を予約します。 包括的なガイダンスについては、 .NETを参照してください。

こちらも参照ください