Operacje na ciągach: dopasowywanie wzorców, wydajność i wyszukiwanie oparte na zakresie

W tym artykule opisano trzy operacje ciągów: wzorzec wyrażeń regularnych zgodny z System.Text.RegularExpressions.Regexwyszukiwaniem bez ReadOnlySpan<T>alokacji w usłudze i wybranie StringComparison wartości dla poprawnych, szybkich porównań.

Znajdowanie określonego tekstu przy użyciu wyrażeń regularnych

Klasa System.Text.RegularExpressions.Regex wyszukuje ciągi dla wzorców, a nie stałych podciągów. Metoda statyczna Regex.IsMatch przyjmuje ciąg wejściowy, wzorzec i opcjonalne RegexOptions flagi.

Poniższy przykład wyszukuje każde zdanie dla wyrazu lub , bez uwzględniania wielkości liter. Wzorzec the(ir)?\s jest opcjonalnie zgodny the z irznakiem , a następnie znakiem odstępu:

Wzór Meaning
the dopasuj tekst literału the
(ir)? dopasowanie 0 lub 1 wystąpienia ir
\s dopasuj znak odstępu
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();
    }
}

Weryfikowanie ciągów względem wzorca

Aby sprawdzić, czy całe dane wejściowe pasują do kształtu, zakotwiczyć wzorzec za pomocą elementu ^ i $. Poniższy przykład sprawdza, czy każdy ciąg jest numerem telefonu w stylu USA: trzy cyfry, trzy cyfry, cztery cyfry, oddzielone kreskami:

Wzór Meaning
^ dopasuj początek ciągu
\d{3} dopasuj dokładnie trzy cyfry
- dopasuj znak literału -
\d{4} dopasuj dokładnie cztery cyfry
$ dopasuj do końca ciągu
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");
}

Aby uzyskać pełną składnię wzorca, zobacz Język wyrażeń regularnych — krótki przewodnik.

Wybieranie metod string i wyrażeń regularnych

string metody i Regex rozwiązywanie nakładających się problemów. Preferuj string metody, gdy wyszukiwany tekst jest wartością literału, znanym prefiksem lub sufiksem albo stałym ogranicznikiem. Są one prostsze do odczytania i szybszego, ponieważ nie płacą kosztów kompilowania i wykonywania wzorca. Regex Osiągaj, gdy element docelowy wyszukiwania jest kształtem, takim jak zmiany, opcjonalne grupy, powtarzające się klasy znaków lub walidacja zakotwiczona. Jeśli możesz napisać wyszukiwanie jako jedno lub dwa string.Contains / / StartsWithIndexOf wywołania, zrób to.

Wyszukiwanie przy użyciu ReadOnlySpan<char>

Podczas analizowania dużych danych wejściowych lub uruchamiania wyszukiwania na ścieżce gorącej string.Substring alokacje poszczególnych wywołań i string.Split mogą dominować. ReadOnlySpan<char> Zapewnia widok na istniejący ciąg (lub tablicę lub bufor stosu) bez kopiowania i MemoryExtensions zapewnia odpowiedniki opartych na zakresie typowych string metod, w tym 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

Wyszukiwanie oparte na zakresie pozwala uniknąć alokacji, ponieważ wycinki (input[start..], rest[..end]) są po prostu oknami nad oryginalnymi znakami. To samo podejście skaluje do analizowania list klucz-wartość, nagłówków i innego tekstu rozdzielanego bez wywoływania metody Substring.

Zagadnienia dotyczące wydajności StringComparison

Większość string metod wystąpienia ma przeciążenia, które akceptują StringComparison wartość. Metody, takie jak String.Equals(String) domyślne, porządkowe, ale String.Compare(String, String) domyślne dla String.IndexOf(String)bieżącej kultury. Ta różnica ma znaczenie na dwa sposoby:

  • Prędkość. Porównanie porządkowe to test bajtowy dla bajtów uruchamiany w ciasnych, wektoryzowanych pętlach. Porównanie obsługujące kulturę sprawdza tabelę sortowania, przeprowadza łączenie znaków i stosuje reguły specyficzne dla ustawień regionalnych. W przypadku tych samych danych wejściowych może to być kolejność wielkości wolniej.
  • Poprawność Porównanie z uwzględnieniem kultury może składać znaki, których nie oczekujesz (turecki i/I, niemiecki ß do ss, ligatur). To zachowanie jest właściwe w przypadku sortowania nazw, które użytkownik widzi, ale nie tak w przypadku analizowania identyfikatorów, ścieżek lub tokenów protokołu.

W przypadku tekstu zdefiniowanego przez maszynę, takiego jak nazwy plików, adresy URL, nagłówki HTTP, identyfikatory i klucze konfiguracji, przekaż StringComparison.Ordinal lub StringComparison.OrdinalIgnoreCase jawnie. Zarezerwuj wartości obsługujące kulturę dla tekstu w języku naturalnym wyświetlanego użytkownikom. Aby uzyskać kompleksowe wskazówki, zobacz Najest rozwiązania dotyczące porównywania ciągów w .NET.

Zobacz także