Najlepsze rozwiązania dotyczące wyrażeń regularnych na platformie .NET
Aparat wyrażeń regularnych na platformie .NET to zaawansowane, w pełni funkcjonalne narzędzie, które przetwarza tekst na podstawie dopasowań wzorca, a nie na porównywaniu i dopasowywaniu tekstu literału. W większości przypadków dopasowanie do wzorca przebiega szybko i skutecznie. Jednak w niektórych przypadkach aparat wyrażeń regularnych może wydawać się powolny. W skrajnych przypadkach może nawet pozornie przestać odpowiadać, ponieważ przetwarza stosunkowo mało danych wejściowych w ciągu kilku godzin lub nawet dni.
W tym artykule opisano niektóre z najlepszych rozwiązań, które deweloperzy mogą przyjąć, aby zapewnić, że ich wyrażenia regularne osiągną optymalną wydajność.
Ostrzeżenie
W przypadku używania System.Text.RegularExpressions metody do przetwarzania niezaufanych danych wejściowych należy przekazać limit czasu. Złośliwy użytkownik może przekazać dane wejściowe , RegularExpressions
powodując atak typu "odmowa usługi". ASP.NET podstawowe interfejsy API platformy, które używają RegularExpressions
przekroczenia limitu czasu.
Rozważ źródło danych wejściowych
Ogólnie rzecz biorąc, wyrażenia regularne mogą przyjmować dwa typy danych wejściowych: ograniczone i nieograniczone. Ograniczone dane wejściowe to tekst pochodzący ze znanego lub niezawodnego źródła i zgodny ze wstępnie zdefiniowanym formatem. Nieprzeciągniętych danych wejściowych jest tekstem pochodzącym z zawodnego źródła, takiego jak użytkownik internetowy, i może nie być zgodne ze wstępnie zdefiniowanym lub oczekiwanym formatem.
Wzorce wyrażeń regularnych są często zapisywane w celu dopasowania do prawidłowych danych wejściowych. Oznacza to, że deweloperzy sprawdzają tekst, który chcą dopasować, a następnie piszą wzorzec wyrażenia regularnego, który mu odpowiada. Następnie deweloperzy określają, czy wzorzec wymaga poprawek, testując go dla wielu prawidłowych elementów wejściowych. Gdy wzorzec pasuje do wszystkich domniemanych prawidłowych danych wejściowych, jest zadeklarowany jako gotowy do produkcji i może zostać uwzględniony w wydanej aplikacji. Takie podejście sprawia, że wzorzec wyrażenia regularnego nadaje się do dopasowywania ograniczonych danych wejściowych. Jednak nie nadaje się do dopasowywania nieprzeciętnych danych wejściowych.
Aby dopasować nieprzeciągniętych danych wejściowych, wyrażenie regularne musi efektywnie obsługiwać trzy rodzaje tekstu:
- Tekst, który pasuje do wzorca wyrażenia regularnego.
- Tekst, który nie jest zgodny ze wzorcem wyrażenia regularnego.
- Tekst, który prawie pasuje do wzorca wyrażenia regularnego.
Ostatni typ tekstu jest szczególnie problematyczny dla wyrażeń regularnych, które zostały napisane do obsługi ograniczonych danych wejściowych. Jeśli to wyrażenie regularne opiera się również na rozbudowanym wycofywaniu, aparat wyrażeń regularnych może poświęcić niesprzedajny czas (w niektórych przypadkach wiele godzin lub dni) przetwarzania pozornie nieszkodliwego tekstu.
Ostrzeżenie
W poniższym przykładzie użyto wyrażenia regularnego, które jest podatne na nadmierne wycofywanie i prawdopodobnie odrzuci prawidłowe adresy e-mail. Nie należy jej używać w procedurze weryfikacji wiadomości e-mail. Jeśli chcesz, aby wyrażenie regularne weryfikuje adresy e-mail, zobacz Instrukcje: sprawdzanie, czy ciągi są w prawidłowym formacie poczty e-mail.
Rozważmy na przykład często używane, ale problematyczne wyrażenie regularne do sprawdzania poprawności aliasu adresu e-mail. Wyrażenie ^[0-9A-Z]([-.\w]*[0-9A-Z])*$
regularne jest zapisywane w celu przetworzenia tego, co jest uważane za prawidłowy adres e-mail. Prawidłowy adres e-mail składa się z znaku alfanumerycznego, po którym następuje zero lub więcej znaków, które mogą być alfanumeryczne, kropki lub łączniki. Wyrażenie regularne musi być zakończone znakiem alfanumerycznym. Jednak jak pokazano w poniższym przykładzie, chociaż to wyrażenie regularne łatwo obsługuje prawidłowe dane wejściowe, jego wydajność jest nieefektywna, gdy przetwarza niemal prawidłowe dane wejściowe:
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
public class DesignExample
{
public static void Main()
{
Stopwatch sw;
string[] addresses = { "AAAAAAAAAAA@contoso.com",
"AAAAAAAAAAaaaaaaaaaa!@contoso.com" };
// The following regular expression should not actually be used to
// validate an email address.
string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])*$";
string input;
foreach (var address in addresses)
{
string mailBox = address.Substring(0, address.IndexOf("@"));
int index = 0;
for (int ctr = mailBox.Length - 1; ctr >= 0; ctr--)
{
index++;
input = mailBox.Substring(ctr, index);
sw = Stopwatch.StartNew();
Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
sw.Stop();
if (m.Success)
Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
index, m.Value, sw.Elapsed);
else
Console.WriteLine("{0,2}. Failed '{1,25}' in {2}",
index, input, sw.Elapsed);
}
Console.WriteLine();
}
}
}
// The example displays output similar to the following:
// 1. Matched ' A' in 00:00:00.0007122
// 2. Matched ' AA' in 00:00:00.0000282
// 3. Matched ' AAA' in 00:00:00.0000042
// 4. Matched ' AAAA' in 00:00:00.0000038
// 5. Matched ' AAAAA' in 00:00:00.0000042
// 6. Matched ' AAAAAA' in 00:00:00.0000042
// 7. Matched ' AAAAAAA' in 00:00:00.0000042
// 8. Matched ' AAAAAAAA' in 00:00:00.0000087
// 9. Matched ' AAAAAAAAA' in 00:00:00.0000045
// 10. Matched ' AAAAAAAAAA' in 00:00:00.0000045
// 11. Matched ' AAAAAAAAAAA' in 00:00:00.0000045
//
// 1. Failed ' !' in 00:00:00.0000447
// 2. Failed ' a!' in 00:00:00.0000071
// 3. Failed ' aa!' in 00:00:00.0000071
// 4. Failed ' aaa!' in 00:00:00.0000061
// 5. Failed ' aaaa!' in 00:00:00.0000081
// 6. Failed ' aaaaa!' in 00:00:00.0000126
// 7. Failed ' aaaaaa!' in 00:00:00.0000359
// 8. Failed ' aaaaaaa!' in 00:00:00.0000414
// 9. Failed ' aaaaaaaa!' in 00:00:00.0000758
// 10. Failed ' aaaaaaaaa!' in 00:00:00.0001462
// 11. Failed ' aaaaaaaaaa!' in 00:00:00.0002885
// 12. Failed ' Aaaaaaaaaaa!' in 00:00:00.0005780
// 13. Failed ' AAaaaaaaaaaa!' in 00:00:00.0011628
// 14. Failed ' AAAaaaaaaaaaa!' in 00:00:00.0022851
// 15. Failed ' AAAAaaaaaaaaaa!' in 00:00:00.0045864
// 16. Failed ' AAAAAaaaaaaaaaa!' in 00:00:00.0093168
// 17. Failed ' AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
// 18. Failed ' AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
// 19. Failed ' AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
// 20. Failed ' AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
// 21. Failed ' AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372
Imports System.Diagnostics
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim sw As Stopwatch
Dim addresses() As String = {"AAAAAAAAAAA@contoso.com",
"AAAAAAAAAAaaaaaaaaaa!@contoso.com"}
' The following regular expression should not actually be used to
' validate an email address.
Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])*$"
Dim input As String
For Each address In addresses
Dim mailBox As String = address.Substring(0, address.IndexOf("@"))
Dim index As Integer = 0
For ctr As Integer = mailBox.Length - 1 To 0 Step -1
index += 1
input = mailBox.Substring(ctr, index)
sw = Stopwatch.StartNew()
Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
sw.Stop()
if m.Success Then
Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
index, m.Value, sw.Elapsed)
Else
Console.WriteLine("{0,2}. Failed '{1,25}' in {2}",
index, input, sw.Elapsed)
End If
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays output similar to the following:
' 1. Matched ' A' in 00:00:00.0007122
' 2. Matched ' AA' in 00:00:00.0000282
' 3. Matched ' AAA' in 00:00:00.0000042
' 4. Matched ' AAAA' in 00:00:00.0000038
' 5. Matched ' AAAAA' in 00:00:00.0000042
' 6. Matched ' AAAAAA' in 00:00:00.0000042
' 7. Matched ' AAAAAAA' in 00:00:00.0000042
' 8. Matched ' AAAAAAAA' in 00:00:00.0000087
' 9. Matched ' AAAAAAAAA' in 00:00:00.0000045
' 10. Matched ' AAAAAAAAAA' in 00:00:00.0000045
' 11. Matched ' AAAAAAAAAAA' in 00:00:00.0000045
'
' 1. Failed ' !' in 00:00:00.0000447
' 2. Failed ' a!' in 00:00:00.0000071
' 3. Failed ' aa!' in 00:00:00.0000071
' 4. Failed ' aaa!' in 00:00:00.0000061
' 5. Failed ' aaaa!' in 00:00:00.0000081
' 6. Failed ' aaaaa!' in 00:00:00.0000126
' 7. Failed ' aaaaaa!' in 00:00:00.0000359
' 8. Failed ' aaaaaaa!' in 00:00:00.0000414
' 9. Failed ' aaaaaaaa!' in 00:00:00.0000758
' 10. Failed ' aaaaaaaaa!' in 00:00:00.0001462
' 11. Failed ' aaaaaaaaaa!' in 00:00:00.0002885
' 12. Failed ' Aaaaaaaaaaa!' in 00:00:00.0005780
' 13. Failed ' AAaaaaaaaaaa!' in 00:00:00.0011628
' 14. Failed ' AAAaaaaaaaaaa!' in 00:00:00.0022851
' 15. Failed ' AAAAaaaaaaaaaa!' in 00:00:00.0045864
' 16. Failed ' AAAAAaaaaaaaaaa!' in 00:00:00.0093168
' 17. Failed ' AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
' 18. Failed ' AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
' 19. Failed ' AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
' 20. Failed ' AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
' 21. Failed ' AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372
Jak pokazano w danych wyjściowych z poprzedniego przykładu, aparat wyrażeń regularnych przetwarza prawidłowy alias wiadomości e-mail w mniej więcej tym samym przedziale czasu niezależnie od jego długości. Z drugiej strony, gdy prawie prawidłowy adres e-mail ma więcej niż pięć znaków, czas przetwarzania około dwukrotnie dla każdego dodatkowego znaku w ciągu. W związku z tym prawie prawidłowy 28-znakowy ciąg zajmie ponad godzinę przetwarzania, a prawie prawidłowy ciąg 33-znakowy zajmie prawie dzień do przetworzenia.
Ponieważ to wyrażenie regularne zostało opracowane wyłącznie przez uwzględnienie formatu danych wejściowych do dopasowania, nie uwzględnia danych wejściowych, które nie są zgodne ze wzorcem. Z kolei ten nadzór może umożliwić nieograniczone wprowadzanie danych wejściowych, które prawie pasują do wzorca wyrażenia regularnego, aby znacznie obniżyć wydajność.
Aby rozwiązać ten problem, można wykonać następujące czynności:
Podczas tworzenia wzorca należy uwzględnić wpływ wycofywania na wydajność aparatu wyrażeń regularnych, szczególnie jeśli wyrażenie regularne jest przeznaczone do przetwarzania nieograniczonych danych wejściowych. Aby uzyskać więcej informacji, zobacz sekcję Take Charge of Backtracking (Przejmowanie wycofywania).
Dokładnie przetestuj wyrażenie regularne przy użyciu nieprawidłowych, prawie prawidłowych i prawidłowych danych wejściowych. Rex umożliwia losowe generowanie danych wejściowych dla określonego wyrażenia regularnego. Rex to narzędzie do eksploracji wyrażeń regularnych firmy Microsoft Research.
Obsługa wystąpienia obiektu odpowiednio
W samym sercu programu . Model obiektu wyrażenia regularnego platformy NET jest klasą System.Text.RegularExpressions.Regex , która reprezentuje aparat wyrażeń regularnych. Często największym czynnikiem wpływającym na wydajność wyrażeń regularnych jest sposób, w jaki Regex jest używany silnik. Definiowanie wyrażenia regularnego polega na ścisłym sprzęganiu aparatu wyrażeń regularnych z wzorcem wyrażenia regularnego. Ten proces sprzęgania jest kosztowny, niezależnie od tego, czy polega na utworzeniu wystąpienia Regex obiektu przez przekazanie jego konstruktora wzorca wyrażenia regularnego, czy wywołanie metody statycznej przez przekazanie go wzorca wyrażenia regularnego i ciągu do przeanalizowania.
Uwaga
Aby zapoznać się ze szczegółowym omówieniem wpływu na wydajność używania interpretowanych i skompilowanych wyrażeń regularnych, zobacz wpis w blogu Optymalizacja wydajności wyrażeń regularnych, część II: Przejmowanie opłat za wycofywanie.
Aparat wyrażeń regularnych można połączyć z określonym wzorcem wyrażeń regularnych, a następnie użyć aparatu, aby dopasować tekst na kilka sposobów:
Możesz wywołać statyczną metodę dopasowywania wzorca, taką jak Regex.Match(String, String). Ta metoda nie wymaga utworzenia wystąpienia obiektu wyrażenia regularnego.
Można utworzyć wystąpienie obiektu i wywołać metodę Regex dopasowywania wzorca wystąpienia interpretowanego wyrażenia regularnego, która jest domyślną metodą powiązania aparatu wyrażeń regularnych z wzorcem wyrażeń regularnych. Powoduje to utworzenie wystąpienia Regex obiektu bez argumentu zawierającego
options
flagę Compiled .Możesz utworzyć wystąpienie obiektu i wywołać metodę Regex dopasowywania wzorca wystąpienia w wyrażeniu regularnym wygenerowanym przez źródło. Ta technika jest zalecana w większości przypadków. W tym celu umieść GeneratedRegexAttribute atrybut w metodzie częściowej, która zwraca
Regex
wartość .Można utworzyć wystąpienie obiektu i wywołać metodę Regex dopasowywania wzorca wystąpienia skompilowanego wyrażenia regularnego. Obiekty wyrażeń regularnych reprezentują skompilowane wzorce, gdy Regex obiekt jest tworzone wystąpienie z argumentem zawierającym
options
flagę Compiled .
Konkretny sposób wywoływania metod dopasowywania wyrażeń regularnych może mieć wpływ na wydajność aplikacji. W poniższych sekcjach omówiono, kiedy używać wywołań metod statycznych, wyrażeń regularnych generowanych przez źródło, interpretowanych wyrażeń regularnych i skompilowanych wyrażeń regularnych w celu poprawy wydajności aplikacji.
Ważne
Forma wywołania metody (statyczna, interpretowana, wygenerowana przez źródło, skompilowana) ma wpływ na wydajność, jeśli to samo wyrażenie regularne jest używane wielokrotnie w wywołaniach metod lub jeśli aplikacja intensywnie korzysta z obiektów wyrażeń regularnych.
Statyczne wyrażenia regularne
Statyczne metody wyrażeń regularnych są zalecane jako alternatywa dla wielokrotnego tworzenia wystąpienia obiektu wyrażenia regularnego z tym samym wyrażeniem regularnym. W przeciwieństwie do wzorców wyrażeń regularnych używanych przez obiekty wyrażeń regularnych, kody operacji (opcodes) lub skompilowany wspólny język pośredni (CIL) z wzorców używanych w wywołaniach metod statycznych są buforowane wewnętrznie przez aparat wyrażeń regularnych.
Na przykład program obsługi zdarzeń często wywołuje inną metodę do weryfikacji danych wejściowych użytkownika. Ten przykład jest odzwierciedlony w poniższym kodzie, w którym Button zdarzenie kontrolki Click jest używane do wywoływania metody o nazwie IsValidCurrency
, która sprawdza, czy użytkownik wprowadził symbol waluty, po którym następuje co najmniej jedna cyfra dziesiętna.
public void OKButton_Click(object sender, EventArgs e)
{
if (! String.IsNullOrEmpty(sourceCurrency.Text))
if (RegexLib.IsValidCurrency(sourceCurrency.Text))
PerformConversion();
else
status.Text = "The source currency value is invalid.";
}
Public Sub OKButton_Click(sender As Object, e As EventArgs) _
Handles OKButton.Click
If Not String.IsNullOrEmpty(sourceCurrency.Text) Then
If RegexLib.IsValidCurrency(sourceCurrency.Text) Then
PerformConversion()
Else
status.Text = "The source currency value is invalid."
End If
End If
End Sub
Nieefektywna implementacja IsValidCurrency
metody jest pokazana w poniższym przykładzie:
Uwaga
Każde wywołanie metody ponownie inicjuje Regex obiekt o tym samym wzorcu. To z kolei oznacza, że wzorzec wyrażenia regularnego należy kompilować podczas każdego wywołania metody.
using System;
using System.Text.RegularExpressions;
public class RegexLib
{
public static bool IsValidCurrency(string currencyValue)
{
string pattern = @"\p{Sc}+\s*\d+";
Regex currencyRegex = new Regex(pattern);
return currencyRegex.IsMatch(currencyValue);
}
}
Imports System.Text.RegularExpressions
Public Module RegexLib
Public Function IsValidCurrency(currencyValue As String) As Boolean
Dim pattern As String = "\p{Sc}+\s*\d+"
Dim currencyRegex As New Regex(pattern)
Return currencyRegex.IsMatch(currencyValue)
End Function
End Module
Powyższy nieefektywny kod należy zastąpić wywołaniem metody statycznej Regex.IsMatch(String, String) . Takie podejście eliminuje konieczność tworzenia wystąpienia Regex obiektu za każdym razem, gdy chcesz wywołać metodę dopasowywania wzorca, i umożliwia aparatowi wyrażeń regularnych pobranie skompilowanej wersji wyrażenia regularnego z pamięci podręcznej.
using System;
using System.Text.RegularExpressions;
public class RegexLib2
{
public static bool IsValidCurrency(string currencyValue)
{
string pattern = @"\p{Sc}+\s*\d+";
return Regex.IsMatch(currencyValue, pattern);
}
}
Imports System.Text.RegularExpressions
Public Module RegexLib
Public Function IsValidCurrency(currencyValue As String) As Boolean
Dim pattern As String = "\p{Sc}+\s*\d+"
Return Regex.IsMatch(currencyValue, pattern)
End Function
End Module
Domyślnie buforowanych jest piętnaście ostatnio używanych statycznych wzorców wyrażeń regularnych. W przypadku aplikacji, które wymagają większej liczby buforowanych statycznych wyrażeń regularnych, rozmiar pamięci podręcznej można dostosować, ustawiając Regex.CacheSize właściwość .
Wyrażenie \p{Sc}+\s*\d+
regularne używane w tym przykładzie sprawdza, czy ciąg wejściowy ma symbol waluty i co najmniej jedną cyfrę dziesiętną. Wzorzec jest zdefiniowany, jak pokazano w poniższej tabeli:
Wzorzec | opis |
---|---|
\p{Sc}+ |
Dopasuje co najmniej jeden znak w kategorii Symbol Unicode, Waluta. |
\s* |
Dopasuje zero lub więcej znaków odstępu. |
\d+ |
Dopasuje co najmniej jedną cyfrę dziesiętną. |
Interpretowane a generowane przez źródło a skompilowane wyrażenia regularne
Wzorce wyrażeń regularnych, które nie są powiązane z aparatem wyrażeń regularnych Compiled za pomocą specyfikacji opcji, są interpretowane. Kiedy tworzone jest wystąpienie obiektu wyrażenia regularnego, aparat wyrażeń regularnych konwertuje wyrażenie regularne na zestaw kodów operacji. Po wywołaniu metody wystąpienia kody operacji są konwertowane na format CIL i wykonywane przez kompilator JIT. Podobnie, gdy wywoływana jest statyczna metoda wyrażenia regularnego i nie można odnaleźć wyrażenia regularnego w pamięci podręcznej, aparat wyrażeń regularnych konwertuje wyrażenie regularne na zestaw kodów operacji i przechowuje je w pamięci podręcznej. Następnie konwertuje te kody operacji na CIL, aby kompilator JIT mógł je wykonać. Interpretowane wyrażenia regularne ograniczają czas uruchamiania kosztem wolniejszego czasu wykonania. W związku z tym procesem najlepiej używać wyrażenia regularnego w niewielkiej liczbie wywołań metod lub jeśli dokładna liczba wywołań metod wyrażeń regularnych jest nieznana, ale oczekuje się, że będzie mała. Wraz ze wzrostem liczby wywołań metod przyrost wydajności związany ze skróceniem czasu uruchamiania jest coraz mniejszy wskutek wolniejszego wykonywania.
Wzorce wyrażeń regularnych powiązane z aparatem wyrażeń regularnych Compiled za pomocą specyfikacji opcji są kompilowane. W związku z tym, gdy wystąpi obiekt wyrażenia regularnego lub gdy jest wywoływana statyczna metoda wyrażenia regularnego, a wyrażenie regularne nie może być znalezione w pamięci podręcznej, aparat wyrażeń regularnych konwertuje wyrażenie regularne na pośredni zestaw kodów operacji. Te kody są następnie konwertowane na format CIL. Po wywołaniu metody kompilator JIT wykonuje element CIL. W przeciwieństwie do interpretowanych wyrażeń regularnych, skompilowane wyrażenia regularne wydłużają czas uruchamiania, ale wykonanie pojedynczych metod dopasowania do wzorca jest szybsze. W rezultacie korzyści w zakresie wydajności wynikające z kompilowania wyrażeń regularnych rosną proporcjonalnie do liczby wywoływanych metod wyrażeń regularnych.
Wzorce wyrażeń regularnych powiązane z aparatem wyrażeń regularnych Regex
za pomocą metody -returning z atrybutem GeneratedRegexAttribute są generowane jako źródło. Generator źródłowy, który podłącza się do kompilatora, emituje jako kod języka C# niestandardową Regex
implementację pochodną z logiką podobną do emisji RegexOptions.Compiled
w wzorniku CIL. Uzyskasz wszystkie korzyści z RegexOptions.Compiled
wydajności przepływności (w rzeczywistości więcej) i korzyści Regex.CompileToAssembly
z uruchamiania programu , ale bez złożoności programu CompileToAssembly
. Źródło, które jest emitowane, jest częścią projektu, co oznacza, że można go również łatwo wyświetlać i debugować.
Podsumowując, zalecamy:
- Użyj interpretowanych wyrażeń regularnych, gdy wywołujesz metody wyrażeń regularnych z określonym wyrażeniem regularnym stosunkowo rzadko.
- Użyj wyrażeń regularnych generowanych przez źródło, jeśli używasz
Regex
w języku C# z argumentami znanymi w czasie kompilacji i używasz stosunkowo często określonego wyrażenia regularnego. - Użyj skompilowanych wyrażeń regularnych podczas wywoływania metod wyrażeń regularnych z określonym wyrażeniem regularnym stosunkowo często i używasz platformy .NET 6 lub starszej wersji.
Trudno jest określić dokładny próg, przy którym wolniejsze szybkości wykonywania interpretowanych wyrażeń regularnych przewyższają zyski ze skróconego czasu uruchamiania. Trudno jest również określić próg, przy którym wolniejsze czasy uruchamiania wygenerowanych lub skompilowanych wyrażeń regularnych przewyższają zyski z szybszych szybkości wykonywania. Progi zależą od różnych czynników, w tym złożoności wyrażenia regularnego i określonych danych, które przetwarza. Aby określić, które wyrażenia regularne oferują najlepszą wydajność dla danego scenariusza aplikacji, możesz użyć Stopwatch klasy , aby porównać ich czasy wykonywania.
W poniższym przykładzie porównano wydajność skompilowanych, wygenerowanych przez źródło i zinterpretowanych wyrażeń regularnych podczas odczytywania pierwszych 10 zdań oraz podczas odczytywania wszystkich zdań w tekście biblioteki Magna Carta williama D. Guthrie i innych adresów. Jak pokazano w danych wyjściowych z przykładu, gdy tylko 10 wywołań jest wykonywane do metod dopasowywania wyrażeń regularnych, interpretowane lub generowane przez źródło wyrażenie regularne zapewnia lepszą wydajność niż skompilowane wyrażenie regularne. Jednak skompilowane wyrażenie regularne oferuje lepszą wydajność dla dużej liczby wywołań (w tym przypadku ponad 13 000).
const string Pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
static readonly HttpClient s_client = new();
[GeneratedRegex(Pattern, RegexOptions.Singleline)]
private static partial Regex GeneratedRegex();
public async static Task RunIt()
{
Stopwatch sw;
Match match;
int ctr;
string text =
await s_client.GetStringAsync("https://www.gutenberg.org/cache/epub/64197/pg64197.txt");
// Read first ten sentences with interpreted regex.
Console.WriteLine("10 Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex int10 = new(Pattern, RegexOptions.Singleline);
match = int10.Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed);
// Read first ten sentences with compiled regex.
Console.WriteLine("10 Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex comp10 = new Regex(Pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = comp10.Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed);
// Read first ten sentences with source-generated regex.
Console.WriteLine("10 Sentences with Source-generated Regex:");
sw = Stopwatch.StartNew();
match = GeneratedRegex().Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed);
// Read all sentences with interpreted regex.
Console.WriteLine("All Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex intAll = new(Pattern, RegexOptions.Singleline);
match = intAll.Match(text);
int matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed);
// Read all sentences with compiled regex.
Console.WriteLine("All Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex compAll = new(Pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = compAll.Match(text);
matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed);
// Read all sentences with source-generated regex.
Console.WriteLine("All Sentences with Source-generated Regex:");
sw = Stopwatch.StartNew();
match = GeneratedRegex().Match(text);
matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed);
return;
}
/* The example displays output similar to the following:
10 Sentences with Interpreted Regex:
10 matches in 00:00:00.0104920
10 Sentences with Compiled Regex:
10 matches in 00:00:00.0234604
10 Sentences with Source-generated Regex:
10 matches in 00:00:00.0060982
All Sentences with Interpreted Regex:
3,427 matches in 00:00:00.1745455
All Sentences with Compiled Regex:
3,427 matches in 00:00:00.0575488
All Sentences with Source-generated Regex:
3,427 matches in 00:00:00.2698670
*/
Wzorzec wyrażeń regularnych używany w przykładzie \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]
, jest zdefiniowany, jak pokazano w poniższej tabeli:
Wzorzec | opis |
---|---|
\b |
Rozpoczyna dopasowanie na granicy wyrazu. |
\w+ |
Pasuje do co najmniej jednego znaku wyrazu. |
(\r?\n)|,?\s) |
Pasuje do zera lub jednego powrotu karetki, po którym następuje znak nowego wiersza albo zero lub jeden przecinek, po którym następuje znak odstępu. |
(\w+((\r?\n)|,?\s))* |
Dopasuje zero lub więcej wystąpień co najmniej jednego znaku słowa, po których następuje zero lub jeden znak powrotu karetki i znak nowego wiersza albo zero lub jeden przecinek, po którym następuje znak odstępu. |
\w+ |
Pasuje do co najmniej jednego znaku wyrazu. |
[.?:;!] |
Pasuje do kropki, znaku zapytania, dwukropka, średnika lub wykrzyknika. |
Przejmowanie wycofywania
Zazwyczaj aparat wyrażeń regularnych używa progresji liniowej do przechodzenia przez ciąg wejściowy i porównywania go ze wzorcem wyrażenia regularnego. Jednak jeśli nieokreślone kwantyfikatory, takie jak *
, +
i ?
są używane we wzorcu wyrażenia regularnego, aparat wyrażeń regularnych może zrezygnować z części pomyślnych częściowych dopasowań i powrócić do wcześniej zapisanego stanu, aby wyszukać pomyślne dopasowanie dla całego wzorca. Proces ten jest znany pod nazwą wycofywania.
Napiwek
Aby uzyskać więcej informacji na temat wycofywania, zobacz Szczegóły zachowania wyrażenia regularnego i wycofywania. Szczegółowe omówienie wycofywania można znaleźć w wpisach w blogu Ulepszenia wyrażeń regularnych na platformie .NET 7 i Optymalizowanie wydajności wyrażeń regularnych.
Obsługa wycofywania daje wyrażeniom regularnym duże możliwości i elastyczność. Dodatkowo odpowiedzialność za kontrolowanie operacji aparatu wyrażeń regularnych spada na deweloperów wyrażeń regularnych. Ponieważ deweloperzy często nie są tego świadomi, błędne użycie wycofywania lub nadmierne poleganie na wycofywaniu często odgrywa najbardziej znaczącą rolę w zmniejszeniu wydajności wyrażeń regularnych. W scenariuszu najgorszego przypadku czas wykonywania może podwajać się dla każdego dodatkowego znaku w ciągu wejściowym. W rzeczywistości, korzystając z wycofywania nadmiernie, łatwo jest utworzyć programowy odpowiednik nieskończonej pętli, jeśli dane wejściowe prawie pasują do wzorca wyrażenia regularnego. Aparat wyrażeń regularnych może potrwać kilka godzin, a nawet dni, aby przetworzyć stosunkowo krótki ciąg wejściowy.
Często aplikacje płacą karę za korzystanie z wycofywania, mimo że wycofywanie nie jest niezbędne dla dopasowania. Na przykład wyrażenie \b\p{Lu}\w*\b
regularne pasuje do wszystkich wyrazów rozpoczynających się od wielkiej litery, jak pokazano w poniższej tabeli:
Wzorzec | opis |
---|---|
\b |
Rozpoczyna dopasowanie na granicy wyrazu. |
\p{Lu} |
Dopasuje wielkie litery. |
\w* |
Dopasuje zero lub więcej znaków wyrazów. |
\b |
Kończy dopasowanie na granicy wyrazu. |
Ponieważ granica wyrazu nie jest taka sama jak lub podzbiór słowa, nie ma możliwości, aby aparat wyrażeń regularnych przekroczył granicę słowa podczas dopasowywania znaków wyrazów. W związku z tym w przypadku tego wyrażenia regularnego wycofywanie nigdy nie może przyczynić się do ogólnego sukcesu każdego dopasowania. Może ona obniżyć wydajność tylko dlatego, że aparat wyrażeń regularnych jest zmuszony do zapisania stanu dla każdego pomyślnego wstępnego dopasowania znaku słowa.
Jeśli okaże się, że wycofywanie nie jest konieczne, można je wyłączyć na kilka sposobów:
Ustawiając opcję (wprowadzoną RegexOptions.NonBacktracking na platformie .NET 7). Aby uzyskać więcej informacji, zobacz Tryb nonbacktracking.
Za pomocą
(?>subexpression)
elementu języka, znanego jako grupa niepodzielna. W poniższym przykładzie jest analizowana składnia ciągu wejściowego przy użyciu dwóch wyrażeń regularnych. Pierwszy element\b\p{Lu}\w*\b
, opiera się na wycofywaniu. Drugi element ,\b\p{Lu}(?>\w*)\b
wyłącza wycofywanie. Jak pokazano w danych wyjściowych z przykładu, oba te elementy generują ten sam wynik:using System; using System.Text.RegularExpressions; public class BackTrack2Example { public static void Main() { string input = "This this word Sentence name Capital"; string pattern = @"\b\p{Lu}\w*\b"; foreach (Match match in Regex.Matches(input, pattern)) Console.WriteLine(match.Value); Console.WriteLine(); pattern = @"\b\p{Lu}(?>\w*)\b"; foreach (Match match in Regex.Matches(input, pattern)) Console.WriteLine(match.Value); } } // The example displays the following output: // This // Sentence // Capital // // This // Sentence // Capital
Imports System.Text.RegularExpressions Module Example Public Sub Main() Dim input As String = "This this word Sentence name Capital" Dim pattern As String = "\b\p{Lu}\w*\b" For Each match As Match In Regex.Matches(input, pattern) Console.WriteLine(match.Value) Next Console.WriteLine() pattern = "\b\p{Lu}(?>\w*)\b" For Each match As Match In Regex.Matches(input, pattern) Console.WriteLine(match.Value) Next End Sub End Module ' The example displays the following output: ' This ' Sentence ' Capital ' ' This ' Sentence ' Capital
W wielu przypadkach wycofywanie jest niezbędne dla dopasowania wzorca wyrażenia regularnego do tekstu wejściowego. Należy pamiętać, że nadmierne używanie wycofywania może poważnie obniżyć wydajność i stworzyć wrażanie, ze aplikacja przestała odpowiadać. W szczególności ten problem pojawia się, gdy kwantyfikatory są zagnieżdżone, a tekst zgodny z podrażeniem zewnętrznym jest podzbiorem tekstu, który pasuje do wewnętrznego podrażenia.
Ostrzeżenie
Oprócz uniknięcia nadmiernego wycofywania należy użyć funkcji limitu czasu, aby upewnić się, że nadmierne wycofywanie nie poważnie obniża wydajności wyrażeń regularnych. Aby uzyskać więcej informacji, zobacz sekcję Use time-out values (Używanie wartości limitu czasu).
Na przykład wzorzec ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$
wyrażenia regularnego jest przeznaczony do dopasowania numeru części składającego się z co najmniej jednego znaku alfanumerycznego. Jakiekolwiek dodatkowe znaki mogą być znakami alfanumerycznymi, łącznikami, podkreśleniami lub kropkami, ale ostatni znak musi być alfanumeryczny. Znak dolara przerywa numer części. W niektórych przypadkach ten wzorzec wyrażenia regularnego może wykazywać niską wydajność, ponieważ kwantyfikatory są zagnieżdżone, a podwyrażenie [0-9A-Z]
jest podzbiorem podwyrażenia [-.\w]*
.
W takich przypadkach można zoptymalizować wydajność wyrażenia regularnego, usuwając zagnieżdżone kwantyfikatory i zastępując zewnętrzne podwyrażenie asercją wyprzedzającą lub wsteczną o zerowej szerokości. Asercji lookahead i lookbehind są kotwicami. Nie przenoszą wskaźnika w ciągu wejściowym, ale zamiast tego patrzą do przodu lub za sobą, aby sprawdzić, czy określony warunek jest spełniony. Na przykład wyrażenie regularne numeru części może zostać przepisane jako ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$
. Ten wzorzec wyrażenia regularnego jest zdefiniowany, jak pokazano w poniższej tabeli:
Wzorzec | opis |
---|---|
^ |
Rozpoczyna dopasowanie na początku ciągu wejściowego. |
[0-9A-Z] |
Dopasowuje znak alfanumeryczny. Numer części musi zawierać przynajmniej jeden znak. |
[-.\w]* |
Dopasowanie do zera lub większej liczby wystąpień dowolnego znaku słowa, łącznika lub kropki. |
\$ |
Dopasowanie do znaku dolara. |
(?<=[0-9A-Z]) |
Spójrz za końcowy znak dolara, aby upewnić się, że poprzedni znak jest alfanumeryczny. |
$ |
Dopasowywanie kończy się na końcu ciągu wejściowego. |
Poniższy przykład ilustruje użycie tego wyrażenia regularnego do dopasowania tablicy zawierającej możliwe numery części:
using System;
using System.Text.RegularExpressions;
public class BackTrack4Example
{
public static void Main()
{
string pattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$";
string[] partNos = { "A1C$", "A4", "A4$", "A1603D$", "A1603D#" };
foreach (var input in partNos)
{
Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine(match.Value);
else
Console.WriteLine("Match not found.");
}
}
}
// The example displays the following output:
// A1C$
// Match not found.
// A4$
// A1603D$
// Match not found.
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim pattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$"
Dim partNos() As String = {"A1C$", "A4", "A4$", "A1603D$",
"A1603D#"}
For Each input As String In partNos
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine(match.Value)
Else
Console.WriteLine("Match not found.")
End If
Next
End Sub
End Module
' The example displays the following output:
' A1C$
' Match not found.
' A4$
' A1603D$
' Match not found.
Język wyrażeń regularnych na platformie .NET zawiera następujące elementy języka, których można użyć do wyeliminowania zagnieżdżonych kwantyfikatorów. Aby uzyskać więcej informacji, zobacz Konstrukcje grupowania.
Element języka | opis |
---|---|
(?= subexpression ) |
Pozytywna asercja wyprzedzająca o zerowej szerokości. Wyprzedza bieżące położenie, aby określić, czy subexpression pasuje do ciągu wejściowego. |
(?! subexpression ) |
Negatywna asercja wyprzedzająca o zerowej szerokości. Przyjrzyj się bieżącemu położeniu, aby określić, czy subexpression ciąg wejściowy nie jest zgodny. |
(?<= subexpression ) |
Pozytywna asercja wsteczna o zerowej szerokości. Szuka bieżącej pozycji, aby określić, czy subexpression pasuje do ciągu wejściowego. |
(?<! subexpression ) |
Negatywna asercja wsteczna o zerowej szerokości. Szuka bieżącego położenia, aby określić, czy subexpression nie jest zgodny z ciągiem wejściowym. |
Użyj wartości limitu czasu
Jeśli wyrażenie regularne przetwarza dane wejściowe, które niemal pasują do wzorca wyrażenia regularnego, często może nadmiernie używać wycofywania, co znacznie wpływa na wydajność. Oprócz dokładnego rozważenia użycia wycofywania i testowania wyrażenia regularnego względem niemal pasujących danych wejściowych należy zawsze ustawić wartość limitu czasu, aby zminimalizować wpływ nadmiernego wycofywania, jeśli wystąpi.
Interwał limitu czasu wyrażenia regularnego definiuje okres, przez który aparat wyrażeń regularnych będzie szukać pojedynczego dopasowania przed upływem limitu czasu. W zależności od wzorca wyrażenia regularnego i tekstu wejściowego czas wykonywania może przekraczać określony interwał limitu czasu, ale nie spędzi więcej czasu na wycofywanie niż określony interwał limitu czasu. Domyślny interwał limitu czasu to Regex.InfiniteMatchTimeout, co oznacza, że limit czasu wyrażenia regularnego nie zostanie przekroczony. Tę wartość można zastąpić i zdefiniować interwał limitu czasu w następujący sposób:
Wywołaj konstruktor, Regex(String, RegexOptions, TimeSpan) aby podać wartość limitu czasu podczas tworzenia wystąpienia Regex obiektu.
Wywołaj metodę dopasowania wzorca statycznego, taką jak Regex.Match(String, String, RegexOptions, TimeSpan) lub Regex.Replace(String, String, String, RegexOptions, TimeSpan), zawierającą
matchTimeout
parametr .Ustaw wartość dla całego procesu lub całej domeny aplikacji z kodem, takim jak
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));
.
Jeśli zdefiniowano interwał limitu czasu i dopasowanie nie zostanie znalezione na końcu tego interwału, metoda wyrażenia regularnego zgłasza RegexMatchTimeoutException wyjątek. W procedurze obsługi wyjątków możesz wybrać ponowienie próby dopasowania z dłuższym interwałem przekroczenia limitu czasu, porzucić próbę dopasowania i założyć, że nie ma dopasowania lub porzucić próbę dopasowania i zarejestrować informacje o wyjątku na potrzeby przyszłej analizy.
W poniższym przykładzie zdefiniowano metodę GetWordData
, która tworzy wystąpienie wyrażenia regularnego z interwałem limitu czasu wynoszącym 350 milisekund w celu obliczenia liczby wyrazów i średniej liczby znaków w słowie w dokumencie tekstowym. Jeśli upłynął limit czasu pasującej operacji, interwał limitu czasu jest zwiększany o 350 milisekund, a Regex obiekt jest potwierdzany ponownie. Jeśli nowy interwał przekroczenia limitu czasu przekracza jedną sekundę, metoda ponownie wywróci wyjątek do elementu wywołującego.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
public class TimeoutExample
{
public static void Main()
{
RegexUtilities util = new RegexUtilities();
string title = "Doyle - The Hound of the Baskervilles.txt";
try
{
var info = util.GetWordData(title);
Console.WriteLine("Words: {0:N0}", info.Item1);
Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2);
}
catch (IOException e)
{
Console.WriteLine("IOException reading file '{0}'", title);
Console.WriteLine(e.Message);
}
catch (RegexMatchTimeoutException e)
{
Console.WriteLine("The operation timed out after {0:N0} milliseconds",
e.MatchTimeout.TotalMilliseconds);
}
}
}
public class RegexUtilities
{
public Tuple<int, double> GetWordData(string filename)
{
const int MAX_TIMEOUT = 1000; // Maximum timeout interval in milliseconds.
const int INCREMENT = 350; // Milliseconds increment of timeout.
List<string> exclusions = new List<string>(new string[] { "a", "an", "the" });
int[] wordLengths = new int[29]; // Allocate an array of more than ample size.
string input = null;
StreamReader sr = null;
try
{
sr = new StreamReader(filename);
input = sr.ReadToEnd();
}
catch (FileNotFoundException e)
{
string msg = String.Format("Unable to find the file '{0}'", filename);
throw new IOException(msg, e);
}
catch (IOException e)
{
throw new IOException(e.Message, e);
}
finally
{
if (sr != null) sr.Close();
}
int timeoutInterval = INCREMENT;
bool init = false;
Regex rgx = null;
Match m = null;
int indexPos = 0;
do
{
try
{
if (!init)
{
rgx = new Regex(@"\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval));
m = rgx.Match(input, indexPos);
init = true;
}
else
{
m = m.NextMatch();
}
if (m.Success)
{
if (!exclusions.Contains(m.Value.ToLower()))
wordLengths[m.Value.Length]++;
indexPos += m.Length + 1;
}
}
catch (RegexMatchTimeoutException e)
{
if (e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT)
{
timeoutInterval += INCREMENT;
init = false;
}
else
{
// Rethrow the exception.
throw;
}
}
} while (m.Success);
// If regex completed successfully, calculate number of words and average length.
int nWords = 0;
long totalLength = 0;
for (int ctr = wordLengths.GetLowerBound(0); ctr <= wordLengths.GetUpperBound(0); ctr++)
{
nWords += wordLengths[ctr];
totalLength += ctr * wordLengths[ctr];
}
return new Tuple<int, double>(nWords, totalLength / nWords);
}
}
Imports System.Collections.Generic
Imports System.IO
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim util As New RegexUtilities()
Dim title As String = "Doyle - The Hound of the Baskervilles.txt"
Try
Dim info = util.GetWordData(title)
Console.WriteLine("Words: {0:N0}", info.Item1)
Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2)
Catch e As IOException
Console.WriteLine("IOException reading file '{0}'", title)
Console.WriteLine(e.Message)
Catch e As RegexMatchTimeoutException
Console.WriteLine("The operation timed out after {0:N0} milliseconds",
e.MatchTimeout.TotalMilliseconds)
End Try
End Sub
End Module
Public Class RegexUtilities
Public Function GetWordData(filename As String) As Tuple(Of Integer, Double)
Const MAX_TIMEOUT As Integer = 1000 ' Maximum timeout interval in milliseconds.
Const INCREMENT As Integer = 350 ' Milliseconds increment of timeout.
Dim exclusions As New List(Of String)({"a", "an", "the"})
Dim wordLengths(30) As Integer ' Allocate an array of more than ample size.
Dim input As String = Nothing
Dim sr As StreamReader = Nothing
Try
sr = New StreamReader(filename)
input = sr.ReadToEnd()
Catch e As FileNotFoundException
Dim msg As String = String.Format("Unable to find the file '{0}'", filename)
Throw New IOException(msg, e)
Catch e As IOException
Throw New IOException(e.Message, e)
Finally
If sr IsNot Nothing Then sr.Close()
End Try
Dim timeoutInterval As Integer = INCREMENT
Dim init As Boolean = False
Dim rgx As Regex = Nothing
Dim m As Match = Nothing
Dim indexPos As Integer = 0
Do
Try
If Not init Then
rgx = New Regex("\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval))
m = rgx.Match(input, indexPos)
init = True
Else
m = m.NextMatch()
End If
If m.Success Then
If Not exclusions.Contains(m.Value.ToLower()) Then
wordLengths(m.Value.Length) += 1
End If
indexPos += m.Length + 1
End If
Catch e As RegexMatchTimeoutException
If e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT Then
timeoutInterval += INCREMENT
init = False
Else
' Rethrow the exception.
Throw
End If
End Try
Loop While m.Success
' If regex completed successfully, calculate number of words and average length.
Dim nWords As Integer
Dim totalLength As Long
For ctr As Integer = wordLengths.GetLowerBound(0) To wordLengths.GetUpperBound(0)
nWords += wordLengths(ctr)
totalLength += ctr * wordLengths(ctr)
Next
Return New Tuple(Of Integer, Double)(nWords, totalLength / nWords)
End Function
End Class
Przechwytywanie tylko wtedy, gdy jest to konieczne
Wyrażenia regularne na platformie .NET obsługują konstrukcje grupowania, które umożliwiają grupowanie wzorca wyrażenia regularnego w co najmniej jedno wyrażenie podrzędne. Najczęściej używane konstrukcje grupowania w języku wyrażeń regularnych platformy .NET to (
podwyrażenie, które definiuje grupę przechwytywania numerowanego i (?<
podwyrażenie)
)
nazw>
, które definiuje nazwaną grupę przechwytywania. Konstrukcje grupujące są niezbędne do tworzenia odwołań wstecznych i do definiowania podwyrażeń, do których jest stosowany kwantyfikator.
Jednak zastosowanie tych elementów języka jest kosztowne. Powodują one, że GroupCollection obiekt zwrócony przez Match.Groups właściwość jest wypełniany najnowszymi nazwami nienazwanych lub nazwanymi przechwytywaniami. Jeśli jedna konstrukcja grupowania przechwyciła wiele podciągów w ciągu wejściowym, wypełniają również CaptureCollection obiekt zwrócony przez Group.Captures właściwość określonej grupy przechwytywania z wieloma Capture obiektami.
Często konstrukcje grupowania są używane tylko w wyrażeniu regularnym, aby można było do nich stosować kwantyfikatory. Grupy przechwycone przez te podwyrażenia nie są używane później. Na przykład wyrażenie \b(\w+[;,]?\s?)+[.?!]
regularne jest przeznaczone do przechwytywania całego zdania. W poniższej tabeli opisano elementy języka w tym wzorcu wyrażenia regularnego oraz ich wpływ na Match kolekcje i Group.Captures obiektyMatch.Groups:
Wzorzec | opis |
---|---|
\b |
Rozpoczyna dopasowanie na granicy wyrazu. |
\w+ |
Pasuje do co najmniej jednego znaku wyrazu. |
[;,]? |
Dopasuje zero lub jeden przecinek lub średnik. |
\s? |
Dopasuje zero lub jeden znak odstępu. |
(\w+[;,]?\s?)+ |
Dopasuje jedno lub więcej wystąpień co najmniej jednego znaku słowa, po którym następuje opcjonalny przecinek lub średnik, po którym następuje opcjonalny znak odstępu. Ten wzorzec definiuje pierwszą grupę przechwytywania, która jest niezbędna, tak aby kombinacja wielu znaków wyrazów (czyli słowa), po których następuje opcjonalny symbol interpunkcyjny, zostanie powtórzona, dopóki aparat wyrażeń regularnych nie osiągnie końca zdania. |
[.?!] |
Pasuje do kropki, znaku zapytania lub wykrzyknika. |
Jak pokazano w poniższym przykładzie, po znalezieniu dopasowania obiekty GroupCollection i CaptureCollection są wypełniane przechwytywaniem z dopasowania. W takim przypadku grupa (\w+[;,]?\s?)
przechwytywania istnieje, aby +
można było do niej zastosować kwantyfikator, co umożliwia dopasowanie wzorca wyrażenia regularnego do każdego wyrazu w zdaniu. W przeciwnym razie dopasowanie nastąpi dla ostatniego wyrazu w zdaniu.
using System;
using System.Text.RegularExpressions;
public class Group1Example
{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(\w+[;,]?\s?)+[.?!]";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index);
int grpCtr = 0;
foreach (Group grp in match.Groups)
{
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index);
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index);
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
// Group 1: 'sentence' at index 12.
// Capture 0: 'This ' at 0.
// Capture 1: 'is ' at 5.
// Capture 2: 'one ' at 8.
// Capture 3: 'sentence' at 12.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
// Group 1: 'another' at index 30.
// Capture 0: 'This ' at 22.
// Capture 1: 'is ' at 27.
// Capture 2: 'another' at 30.
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(\w+[;,]?\s?)+[.?!]"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
' Group 1: 'sentence' at index 12.
' Capture 0: 'This ' at 0.
' Capture 1: 'is ' at 5.
' Capture 2: 'one ' at 8.
' Capture 3: 'sentence' at 12.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.
' Group 1: 'another' at index 30.
' Capture 0: 'This ' at 22.
' Capture 1: 'is ' at 27.
' Capture 2: 'another' at 30.
Jeśli używasz podwyrażenia tylko do stosowania kwantyfikatorów do nich i nie interesuje Cię przechwycony tekst, należy wyłączyć przechwytywanie grup. Na przykład element języka uniemożliwia grupie, (?:subexpression)
do której ma zastosowanie przechwytywanie pasujących podciągów. W poniższym przykładzie wzorzec wyrażenia regularnego z poprzedniego przykładu został zmieniony na \b(?:\w+[;,]?\s?)+[.?!]
. Jak pokazano w danych wyjściowych, aparat wyrażeń regularnych nie wypełnia GroupCollection kolekcji i CaptureCollection :
using System;
using System.Text.RegularExpressions;
public class Group2Example
{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(?:\w+[;,]?\s?)+[.?!]";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index);
int grpCtr = 0;
foreach (Group grp in match.Groups)
{
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index);
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index);
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(?:\w+[;,]?\s?)+[.?!]"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.
Przechwytywanie można wyłączyć na jeden z poniższych sposobów:
(?:subexpression)
Użyj elementu language. Ten element zapobiega przechwytywaniu dopasowanych podciągów w grupie, do której jest stosowany. Nie wyłącza przechwytywania podciągów w żadnych grupach zagnieżdżonych.ExplicitCapture Użyj opcji . Wyłącza wszystkie nienazwane lub niejawne przechwytywania we wzorcu wyrażenia regularnego. W przypadku korzystania z tej opcji można przechwycić tylko podciągy zgodne z nazwanymi grupami zdefiniowanymi za pomocą
(?<name>subexpression)
elementu języka. Flagę ExplicitCapture można przekazać dooptions
parametru konstruktora Regex klasy luboptions
parametru statycznej Regex metody dopasowania.n
Użyj opcji w elemecie(?imnsx)
języka. Powoduje to wyłączenie wszystkich nienazwanych lub niejawnych przechwytywań od miejsca we wzorcu wyrażenia regularnego, w którym znajduje się ten element. Przechwytywanie jest wyłączone do końca wzorca lub do momentu(-n)
włączenia opcji nienazwanych lub niejawnych przechwytywania. Aby uzyskać więcej informacji, zobacz Różne konstrukcje.n
Użyj opcji w elemecie(?imnsx:subexpression)
języka. Ta opcja wyłącza wszystkie nienazwane lub niejawne przechwytywanie w programiesubexpression
. Przechwytywania przez jakiekolwiek nienazwane lub niejawne zagnieżdżone grupy przechwytywania również są wyłączone.
Bezpieczeństwo wątkowe
Sama Regex klasa jest bezpieczna wątkowo i niezmienna (tylko do odczytu). Oznacza to, że Regex
obiekty można tworzyć w dowolnym wątku i współdzielić między wątkami. Metody dopasowywania mogą być wywoływane z dowolnego wątku i nigdy nie zmieniają żadnego stanu globalnego.
Jednak obiekty wynikowe (Match
i MatchCollection
) zwracane przez Regex
powinny być używane w jednym wątku. Chociaż wiele z tych obiektów jest logicznie niezmiennych, ich implementacje mogą opóźnić obliczanie niektórych wyników, aby poprawić wydajność, a w rezultacie obiekty wywołujące muszą serializować dostęp do nich.
Jeśli potrzebujesz współużytkować Regex
obiekty wyników w wielu wątkach, te obiekty można przekonwertować na wystąpienia bezpieczne wątkowo, wywołując ich zsynchronizowane metody. Z wyjątkiem modułów wyliczających wszystkie klasy wyrażeń regularnych są bezpieczne wątkowo lub mogą być konwertowane na obiekty bezpieczne wątkowo przez zsynchronizowaną metodę.
Moduły wyliczania są jedynym wyjątkiem. Należy serializować wywołania do modułów wyliczania kolekcji. Reguła polega na tym, że jeśli kolekcję można wyliczyć jednocześnie na więcej niż jeden wątek, należy zsynchronizować metody modułu wyliczającego na obiekcie głównym kolekcji przechodzącej przez moduł wyliczający.
Powiązane artykuły
Nazwa | opis |
---|---|
Szczegóły dotyczące zachowania wyrażeń regularnych | Analizuje implementację aparatu wyrażeń regularnych na platformie .NET. Artykuł koncentruje się na elastyczności wyrażeń regularnych i wyjaśnia, jak deweloper ponosi odpowiedzialność za zapewnienie wydajnej i niezawodnej pracy aparatu wyrażeń regularnych. |
Śledzenie wsteczne | Informacje, co to jest wycofywanie i jak wpływa na wydajność wyrażeń regularnych oraz analiza elementów języka, które dostarczają alternatywy dla wycofywania. |
Język wyrażeń regularnych — podręczny wykaz | Opisuje elementy języka wyrażeń regularnych na platformie .NET i zawiera linki do szczegółowej dokumentacji dla każdego elementu języka. |