Visszakövetés reguláris kifejezésekben

A visszakövetés akkor fordul elő, ha egy reguláris kifejezésminta opcionális kvantitatív vagy váltakozó szerkezeteket tartalmaz, és a reguláris kifejezésmotor visszatér egy korábbi mentett állapotba, hogy folytassa a keresését egyezésre. A visszakövetés központi szerepet jelent a reguláris kifejezésekben; lehetővé teszi, hogy a kifejezések hatékonyak és rugalmasak legyenek, és nagyon összetett mintákhoz igazodjanak. Ugyanakkor ez az energia költséges. A visszakövetés gyakran az egyetlen legfontosabb tényező, amely befolyásolja a normál kifejezésmotor teljesítményét. Szerencsére a fejlesztő szabályozza a normál kifejezésmotor viselkedését és a visszakövetés használatát. Ez a témakör bemutatja, hogyan működik a visszakövetés, és hogyan szabályozható.

Figyelmeztetés

System.Text.RegularExpressions A nem megbízható bemenetek feldolgozásakor időtúllépést kell megadni. A rosszindulatú felhasználók adhatnak RegularExpressionsmeg bemenetet a szolgáltatásmegtagadási támadáshoz. ASP.NET core framework API-k, amelyek időtúllépést használnak RegularExpressions .

Lineáris összehasonlítás visszalépés nélkül

Ha egy reguláris kifejezésminta nem rendelkezik opcionális kvantitatív vagy váltakozó szerkezettel, a reguláris kifejezésmotor lineáris időben fut. Vagyis miután a reguláris kifejezésmotor megegyezik a minta első nyelvi elemével a bemeneti sztringben lévő szöveggel, megpróbál megegyezni a minta következő nyelvi elemével a bemeneti sztring következő karakterével vagy karaktercsoportjával. Ez mindaddig folytatódik, amíg az egyezés sikeres vagy sikertelen lesz. A reguláris kifejezésmotor mindkét esetben egy karakterrel halad előre a bemeneti sztringben.

Az alábbi példa egy illusztrációt tartalmaz. A reguláris kifejezés e{2}\w\b az "e" betű két előfordulását keresi, amelyet bármely szókarakte után egy szóhatár követ.

using System;
using System.Text.RegularExpressions;

public class Example1
{
    public static void Run()
    {
        string input = "needing a reed";
        string pattern = @"e{2}\w\b";
        foreach (Match match in Regex.Matches(input, pattern))
            Console.WriteLine("{0} found at position {1}",
                              match.Value, match.Index);
    }
}
// The example displays the following output:
//       eed found at position 11
Imports System.Text.RegularExpressions

Module Example1
    Public Sub Run()
        Dim input As String = "needing a reed"
        Dim pattern As String = "e{2}\w\b"
        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("{0} found at position {1}",
                              match.Value, match.Index)
        Next
    End Sub
End Module
' The example displays the following output:
'       eed found at position 11

Bár ez a reguláris kifejezés tartalmazza a kvantitálót {2}, a kiértékelése lineáris módon történik. A reguláris kifejezésmotor nem von vissza, mert {2} nem választható kvantitatív; pontos számot ad meg, nem pedig az előző alkifejezés számának egyező változószámát. Ennek eredményeképpen a reguláris kifejezésmotor megpróbálja egyezni a reguláris kifejezésmintával a bemeneti sztringgel, ahogyan az az alábbi táblázatban látható.

Művelet Pozíció a mintában Pozíció a sztringben Eredmény
0 e "jáva szükséges" (0. index) Nincs egyezés.
2 e "1. index" (1. index) Lehetséges egyezés.
3 E{2} "jávai eding" (2. index) Lehetséges egyezés.
4 \W "ding a reed" (index 3) Lehetséges egyezés.
5 \B "tavazás" (4. index) A lehetséges egyezés meghiúsul.
6 e "jávai eding" (2. index) Lehetséges egyezés.
7 E{2} "ding a reed" (index 3) A lehetséges egyezés meghiúsul.
8 e "ding a reed" (index 3) Az egyezés meghiúsul.
9 e "tavazás" (4. index) Nincs egyezés.
10 e "ng a reed" (5. index) Nincs egyezés.
11 e "g a reed" (6. index) Nincs egyezés.
12 e " a reed" (7. index) Nincs egyezés.
13 e "a reed" (index 8) Nincs egyezés.
14 e " reed" (index 9) Nincs egyezés.
15 e "reed" (index 10) Nincs egyezés
16 e "eed" (index 11) Lehetséges egyezés.
17 E{2} "ed" (index 12) Lehetséges egyezés.
18 \W "d" (13. index) Lehetséges egyezés.
19 \B "" (index 14) Mérkőzés.

Ha egy reguláris kifejezésminta nem tartalmaz opcionális kvantitatív vagy váltakozó szerkezeteket, a reguláris kifejezés mintájának és a bemeneti sztringnek megfelelő összehasonlítások maximális száma nagyjából megegyezik a bemeneti sztringben szereplő karakterek számával. Ebben az esetben a reguláris kifejezésmotor 19 összehasonlítást használ a lehetséges egyezések azonosításához ebben a 13 karakteres sztringben. Más szóval a reguláris kifejezésmotor közel lineáris időben fut, ha nem tartalmaz opcionális kvantitátorokat vagy váltakozó szerkezeteket.

Visszakövetés opcionális kvantitatív vagy váltakozó szerkezetekkel

Ha egy reguláris kifejezés opcionális kvantitatív vagy váltakozó szerkezeteket tartalmaz, a bemeneti sztring kiértékelése már nem lineáris. A nemdeterminista véges automaton (NFA) motorral való egyeztetést a normál kifejezés nyelvi elemei vezérlik, nem pedig a bemeneti sztringben egyeztetendő karakterek. Ezért a reguláris kifejezésmotor megpróbálja teljes mértékben megfeleltetni az opcionális vagy alternatív alkifejezéseket. Ha az alkifejezés következő nyelvi elemére lép, és az egyezés sikertelen, a reguláris kifejezésmotor megszakíthatja a sikeres egyezés egy részét, és visszatérhet egy korábbi mentett állapotba annak érdekében, hogy a reguláris kifejezés egészét egyezzen a bemeneti sztringgel. Ezt a folyamatot vissza kell térni egy korábbi mentett állapotba egyezés megkereséséhez, ezt nevezik visszakövetésnek.

Vegyük például a reguláris kifejezésmintát .*(es), amely megfelel az "es" karakternek és az azt megelőző összes karakternek. Ahogy az alábbi példa is mutatja, ha a bemeneti sztring "Alapvető szolgáltatásokat normál kifejezések biztosítják", a minta megfelel a teljes sztringnek, beleértve az "es" értéket a "kifejezésekben".

using System;
using System.Text.RegularExpressions;

public class Example2
{
    public static void Run()
    {
        string input = "Essential services are provided by regular expressions.";
        string pattern = ".*(es)";
        Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
        if (m.Success)
        {
            Console.WriteLine("'{0}' found at position {1}",
                              m.Value, m.Index);
            Console.WriteLine("'es' found at position {0}",
                              m.Groups[1].Index);
        }
    }
}
//    'Essential services are provided by regular expressions found at position 0
//    'es' found at position 47
Imports System.Text.RegularExpressions

Module Example2
    Public Sub Run()
        Dim input As String = "Essential services are provided by regular expressions."
        Dim pattern As String = ".*(es)"
        Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
        If m.Success Then
            Console.WriteLine("'{0}' found at position {1}",
                              m.Value, m.Index)
            Console.WriteLine("'es' found at position {0}",
                              m.Groups(1).Index)
        End If
    End Sub
End Module
'    'Essential services are provided by regular expressions found at position 0
'    'es' found at position 47

Ehhez a reguláris kifejezésmotor az alábbiak szerint végez visszakövetést:

  • Egyezik a .* teljes bemeneti sztringgel (amely nullával, egy vagy több karakterrel egyezik meg).

  • Megkísérli az "e" egyezését a reguláris kifejezési mintában. A beviteli sztringnek azonban nincs megadva egyező karaktere.

  • Visszalép az utolsó sikeres egyezésre, "Az alapvető szolgáltatásokat reguláris kifejezések biztosítják", és megpróbálja egyezni az "e" kifejezéssel a mondat végén lévő ponttal. Az egyezés meghiúsul.

  • Továbbra is visszalép egy korábbi sikeres egyezésre egy karakterrel egy időben, amíg a feltételesen egyeztetett részszúrás "Alapvető szolgáltatásokat a szokásos expr nyújt". Ezután összehasonlítja a mintában szereplő "e" értéket a második "e" "e" kifejezéssel, és megkeres egyezést.

  • Összehasonlítja a mintában szereplő "s" karaktert az "s"-sel, amely az "e" karaktert követi (az első "s" a "kifejezésekben"). Az egyezés sikeres.

Ha visszalépést használ, a normál kifejezésminta és az 55 karakter hosszú beviteli sztring egyeztetése 67 összehasonlító műveletet igényel. Ha egy reguláris kifejezésminta egyetlen váltakozó szerkezettel vagy egyetlen opcionális kvantitátorsal rendelkezik, a mintának megfelelő összehasonlítási műveletek száma több mint kétszerese a bemeneti sztringben szereplő karakterek számának.

Visszakövetés beágyazott opcionális kvantitatívokkal

A reguláris kifejezésminta egyeztetéséhez szükséges összehasonlító műveletek száma exponenciálisan növekedhet, ha a minta nagy számú váltakozó szerkezetet tartalmaz, ha beágyazott váltakozó szerkezeteket tartalmaz, vagy leggyakrabban beágyazott opcionális kvantitatív elemeket is tartalmaz. A reguláris kifejezésminta ^(a+)+$ például úgy van kialakítva, hogy megfeleljen egy teljes sztringnek, amely egy vagy több "a" karaktert tartalmaz. A példa két azonos hosszúságú bemeneti sztringet tartalmaz, de csak az első sztring felel meg a mintának. Az System.Diagnostics.Stopwatch osztály határozza meg, hogy mennyi ideig tart az egyeztetési művelet.

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example3
{
    public static void Run()
    {
        string pattern = "^(a+)+$";
        string[] inputs = { "aaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaa!" };
        Regex rgx = new Regex(pattern);
        Stopwatch sw;

        foreach (string input in inputs)
        {
            sw = Stopwatch.StartNew();
            Match match = rgx.Match(input);
            sw.Stop();
            if (match.Success)
                Console.WriteLine($"Matched {match.Value} in {sw.Elapsed}");
            else
                Console.WriteLine($"No match found in {sw.Elapsed}");
        }
    }
}
//    Matched aaaaaaaaaaaaaaaaaaaaaaaaaaa in 00:00:00.0018281
//    No match found in 00:00:05.1882144
Imports System.Text.RegularExpressions

Module Example3
    Public Sub Run()
        Dim pattern As String = "^(a+)+$"
        Dim inputs() As String = {"aaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaa!"}
        Dim rgx As New Regex(pattern)
        Dim sw As Stopwatch

        For Each input As String In inputs
            sw = Stopwatch.StartNew()
            Dim match As Match = rgx.Match(input)
            sw.Stop()
            If match.Success Then
                Console.WriteLine("Matched {0} in {1}", match.Value, sw.Elapsed)
            Else
                Console.WriteLine("No match found in {0}", sw.Elapsed)
            End If
        Next
    End Sub
End Module
'    Matched aaaaaaaaaaaaaaaaaaaaaaaaaaa in 00:00:00.0018281
'    No match found in 00:00:05.1882144

Ahogy a példa kimenete is mutatja, a reguláris kifejezésmotor jelentősen tovább tartott, amíg kiderült, hogy egy bemeneti sztring nem egyezik meg a mintával, mivel az egyező sztringet azonosította. Ennek az az oka, hogy a sikertelen egyezés mindig egy legrosszabb esetet jelöl. A reguláris kifejezésmotornak a reguláris kifejezéssel kell követnie az összes lehetséges elérési utat az adatokon, mielőtt arra következtethet, hogy az egyezés sikertelen, és a beágyazott zárójelek számos további elérési utat hoznak létre az adatokon keresztül. A reguláris kifejezésmotor arra a következtetésre jut, hogy a második sztring nem felelt meg a mintának a következő lépésekkel:

  • Ellenőrzi, hogy a sztring elején volt-e, majd megegyezik a sztring első öt karakterével a mintával a+. Ez azt határozza meg, hogy a sztringben nincsenek további "a" karakterek. Végül teszteli a sztring végét. Mivel egy további karakter marad a sztringben, az egyezés meghiúsul. Ez a sikertelen egyezés 9 összehasonlítást igényel. A reguláris kifejezésmotor az állapotinformációkat is menti az "a" (1. találat), az "aa" (2. találat), az "aaa" (3. találat) és az "aaaa" (4. találat) találataiból.

  • Visszatér a korábban mentett 4. találathoz. Azt határozza meg, hogy van-e további "a" karakter egy további rögzített csoporthoz rendelve. Végül teszteli a sztring végét. Mivel egy további karakter marad a sztringben, az egyezés meghiúsul. Ez a sikertelen egyezés 4 összehasonlítást igényel. Eddig összesen 13 összehasonlítást végeztek.

  • Visszatér a korábban mentett 3. találathoz. Megállapítja, hogy két további "a" karaktert kell hozzárendelni egy további rögzített csoporthoz. A sztring végének tesztelése azonban meghiúsul. Ezután visszatér a 3-ashoz, és megpróbál megegyezni két további rögzített csoport két további "a" karakterével. A sztringvégi teszt továbbra is sikertelen. Ezek a sikertelen egyezések 12 összehasonlítást igényelnek. Eddig összesen 25 összehasonlítást végeztek.

A bemeneti sztring és a reguláris kifejezés összehasonlítása addig folytatódik, amíg a reguláris kifejezésmotor az összes lehetséges egyezés-kombinációt ki nem próbálta, majd arra a következtetésre jut, hogy nincs egyezés. A beágyazott kvantátorok miatt ez az összehasonlítás O(2n) vagy exponenciális művelet, ahol n a bemeneti sztringben szereplő karakterek száma. Ez azt jelenti, hogy a legrosszabb esetben egy 30 karakterből álló bemeneti sztringhez körülbelül 1 073 741 824 összehasonlítás szükséges, a 40 karakterből álló bemeneti sztring pedig körülbelül 1 099 511 627 776 összehasonlítást igényel. Ha ilyen vagy még nagyobb hosszúságú sztringeket használ, a reguláris kifejezési metódusok végrehajtása rendkívül hosszú időt vehet igénybe, amikor olyan bemenetet dolgoznak fel, amely nem felel meg a normál kifejezési mintának.

Visszakövetés szabályozása

A visszakövetés lehetővé teszi, hogy hatékony, rugalmas reguláris kifejezéseket hozzon létre. Azonban, ahogy az előző szakaszban is megmutatták, ezek az előnyök elfogadhatatlanul gyenge teljesítménnyel párosulhatnak. A túlzott visszakövetés megakadályozása érdekében meg kell határoznia egy időtúllépési időközt, amikor példányt hoz létre egy Regex objektumban, vagy statikus reguláris kifejezésmegfelelő metódust hív meg. Ezt a következő szakaszban tárgyaljuk. A .NET emellett három reguláris kifejezésnyelvi elemet is támogat, amelyek korlátozzák vagy letiltják a visszakövetést, és amelyek összetett reguláris kifejezéseket támogatnak, amelyek teljesítménybeli büntetés nélkül vagy egyáltalán nem járnak teljesítménnyel: atomi csoportok, lookbehind-állítások és lookahead-állítások. További információ az egyes nyelvi elemekről: Csoportosítási szerkezetek.

Nem visszakövetett reguláris kifejezésmotor

Ha nem kell olyan szerkezeteket használnia, amelyek visszakövetést igényelnek (például keresési megoldások, háttérrendszerek vagy atomi csoportok), fontolja meg a RegexOptions.NonBacktracking mód használatát. Ez a mód úgy van kialakítva, hogy a bemenet hosszával arányos időben hajtsa végre. További információ: NonBacktracking mód. Időtúllépési értéket is beállíthat.

Bemenetek méretének korlátozása

Egyes reguláris kifejezések elfogadható teljesítménnyel rendelkeznek, kivéve, ha a bemenet kivételesen nagy. Ha a forgatókönyv minden ésszerű szöveges bemenete ismert, hogy egy bizonyos hossz alatt van, fontolja meg a hosszabb bemenetek elutasítását, mielőtt a reguláris kifejezést alkalmazva alkalmaznál rájuk.

Időtúllépési időköz megadása

Megadhat egy időtúllépési értéket, amely azt a leghosszabb időközt jelöli, amelyet a reguláris kifejezésmotor egyetlen egyezésre keres, mielőtt megszakítja a kísérletet, és kivételt RegexMatchTimeoutException vet ki. Az időtúllépési időközt úgy adhatja meg, hogy értéket ad TimeSpan meg a Regex(String, RegexOptions, TimeSpan) konstruktornak például a reguláris kifejezésekhez. Emellett minden statikus mintaegyező metódus túlterhelt egy TimeSpan paraméterrel, amely lehetővé teszi egy időtúllépési érték megadását.

Ha nem állít be explicit módon időtúllépési értéket, az alapértelmezett időtúllépési érték a következőképpen lesz meghatározva:

  • Ha létezik ilyen, használja az alkalmazásszintű időtúllépési értéket. Ez lehet bármely időtúllépési érték, amely arra az alkalmazástartományra vonatkozik, amelyben az Regex objektum példányosított, vagy a statikus metódus hívása történik. Az alkalmazásszintű időtúllépési értéket úgy állíthatja be, hogy meghívja a AppDomain.SetData metódust, hogy az érték sztring-ábrázolását TimeSpan rendelje hozzá a "REGEX_DEFAULT_MATCH_TIMEOUT" tulajdonsághoz.
  • Az érték InfiniteMatchTimeouthasználatával, ha nincs alkalmazásszintű időtúllépési érték beállítva.

Alapértelmezés szerint az időtúllépési időköz be van állítva Regex.InfiniteMatchTimeout , és a normál kifejezésmotor nem időtúllépést okoz.

Fontos

Ha nem használja RegexOptions.NonBacktracking, javasoljuk, hogy mindig állítson be időtúllépési időközt, ha a reguláris kifejezés a visszakövetésre támaszkodik, vagy nem megbízható bemeneteken működik.

A RegexMatchTimeoutException kivétel azt jelzi, hogy a normál kifejezésmotor nem talált egyezést a megadott időkorláton belül, de nem jelzi, hogy miért történt a kivétel. Ennek oka lehet a túlzott visszakövetés, de az is lehetséges, hogy az időtúllépési időköz túl alacsony volt, mivel a rendszer terhelése a kivétel ki lett dobva. A kivétel kezelésekor dönthet úgy, hogy megszakítja a további egyezéseket a bemeneti sztringgel, vagy növeli az időtúllépési időközt, és újrapróbálkozza a megfelelő műveletet.

Az alábbi kód például meghívja a Regex(String, RegexOptions, TimeSpan) konstruktort egy Regex 1 másodperces időtúllépési értékkel rendelkező objektum példányosítására. A reguláris kifejezésminta (a+)+$, amely egy vagy több sor végén egy vagy több "a" karaktersorozattal egyezik, túlzott visszakövetésnek van kitéve. Ha egy RegexMatchTimeoutException dobás történik, a példa legfeljebb 3 másodperces időtartamra növeli az időtúllépési értéket. Ezt követően megszakítja a minta egyezésére tett kísérletet.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Security;
using System.Text.RegularExpressions;
using System.Threading;

public class Example
{
    const int MaxTimeoutInSeconds = 3;

    public static void Main()
    {
        string pattern = @"(a+)+$";    // DO NOT REUSE THIS PATTERN.
        Regex rgx = new Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
        Stopwatch? sw = null;

        string[] inputs = { "aa", "aaaa>",
                         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                         "aaaaaaaaaaaaaaaaaaaaaa>",
                         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>" };

        foreach (var inputValue in inputs)
        {
            Console.WriteLine("Processing {0}", inputValue);
            bool timedOut = false;
            do
            {
                try
                {
                    sw = Stopwatch.StartNew();
                    // Display the result.
                    if (rgx.IsMatch(inputValue))
                    {
                        sw.Stop();
                        Console.WriteLine(@"Valid: '{0}' ({1:ss\.fffffff} seconds)",
                                          inputValue, sw.Elapsed);
                    }
                    else
                    {
                        sw.Stop();
                        Console.WriteLine(@"'{0}' is not a valid string. ({1:ss\.fffff} seconds)",
                                          inputValue, sw.Elapsed);
                    }
                }
                catch (RegexMatchTimeoutException e)
                {
                    sw.Stop();
                    // Display the elapsed time until the exception.
                    Console.WriteLine(@"Timeout with '{0}' after {1:ss\.fffff}",
                                      inputValue, sw.Elapsed);
                    Thread.Sleep(1500);       // Pause for 1.5 seconds.

                    // Increase the timeout interval and retry.
                    TimeSpan timeout = e.MatchTimeout.Add(TimeSpan.FromSeconds(1));
                    if (timeout.TotalSeconds > MaxTimeoutInSeconds)
                    {
                        Console.WriteLine("Maximum timeout interval of {0} seconds exceeded.",
                                          MaxTimeoutInSeconds);
                        timedOut = false;
                    }
                    else
                    {
                        Console.WriteLine("Changing the timeout interval to {0}",
                                          timeout);
                        rgx = new Regex(pattern, RegexOptions.IgnoreCase, timeout);
                        timedOut = true;
                    }
                }
            } while (timedOut);
            Console.WriteLine();
        }
    }
}
// The example displays output like the following :
//    Processing aa
//    Valid: 'aa' (00.0000779 seconds)
//
//    Processing aaaa>
//    'aaaa>' is not a valid string. (00.00005 seconds)
//
//    Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
//    Valid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' (00.0000043 seconds)
//
//    Processing aaaaaaaaaaaaaaaaaaaaaa>
//    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 01.00469
//    Changing the timeout interval to 00:00:02
//    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 02.01202
//    Changing the timeout interval to 00:00:03
//    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 03.01043
//    Maximum timeout interval of 3 seconds exceeded.
//
//    Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
//    Timeout with 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>' after 03.01018
//    Maximum timeout interval of 3 seconds exceeded.
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Security
Imports System.Text.RegularExpressions
Imports System.Threading

Module Example
    Const MaxTimeoutInSeconds As Integer = 3

    Public Sub Main()
        Dim pattern As String = "(a+)+$"    ' DO NOT REUSE THIS PATTERN.
        Dim rgx As New Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1))
        Dim sw As Stopwatch = Nothing

        Dim inputs() As String = {"aa", "aaaa>",
                                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                                   "aaaaaaaaaaaaaaaaaaaaaa>",
                                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>"}

        For Each inputValue In inputs
            Console.WriteLine("Processing {0}", inputValue)
            Dim timedOut As Boolean = False
            Do
                Try
                    sw = Stopwatch.StartNew()
                    ' Display the result.
                    If rgx.IsMatch(inputValue) Then
                        sw.Stop()
                        Console.WriteLine("Valid: '{0}' ({1:ss\.fffffff} seconds)",
                                          inputValue, sw.Elapsed)
                    Else
                        sw.Stop()
                        Console.WriteLine("'{0}' is not a valid string. ({1:ss\.fffff} seconds)",
                                          inputValue, sw.Elapsed)
                    End If
                Catch e As RegexMatchTimeoutException
                    sw.Stop()
                    ' Display the elapsed time until the exception.
                    Console.WriteLine("Timeout with '{0}' after {1:ss\.fffff}",
                                      inputValue, sw.Elapsed)
                    Thread.Sleep(1500)       ' Pause for 1.5 seconds.

                    ' Increase the timeout interval and retry.
                    Dim timeout As TimeSpan = e.MatchTimeout.Add(TimeSpan.FromSeconds(1))
                    If timeout.TotalSeconds > MaxTimeoutInSeconds Then
                        Console.WriteLine("Maximum timeout interval of {0} seconds exceeded.",
                                          MaxTimeoutInSeconds)
                        timedOut = False
                    Else
                        Console.WriteLine("Changing the timeout interval to {0}",
                                          timeout)
                        rgx = New Regex(pattern, RegexOptions.IgnoreCase, timeout)
                        timedOut = True
                    End If
                End Try
            Loop While timedOut
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays output like the following:
'    Processing aa
'    Valid: 'aa' (00.0000779 seconds)
'    
'    Processing aaaa>
'    'aaaa>' is not a valid string. (00.00005 seconds)
'    
'    Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
'    Valid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' (00.0000043 seconds)
'    
'    Processing aaaaaaaaaaaaaaaaaaaaaa>
'    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 01.00469
'    Changing the timeout interval to 00:00:02
'    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 02.01202
'    Changing the timeout interval to 00:00:03
'    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 03.01043
'    Maximum timeout interval of 3 seconds exceeded.
'    
'    Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
'    Timeout with 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>' after 03.01018
'    Maximum timeout interval of 3 seconds exceeded.

Atomi csoportok

A (?>szubexpressziós) nyelvi elem egy atomi csoportosítás. Megakadályozza a visszalépést a szubexpresszióba. Ha ez a nyelvi elem sikeresen megfelelt, az nem adja fel a későbbi visszakövetéshez való egyezés egyik részét sem. A mintában (?>\w*\d*)1például , ha a 1 nem lehet egyezni, akkor \d* sem adja fel az egyezést, még akkor sem, ha ez azt jelenti, hogy lehetővé teszi a 1 sikeres egyeztetést. Az atomi csoportok segíthetnek megelőzni a sikertelen egyezésekkel kapcsolatos teljesítményproblémát.

Az alábbi példa bemutatja, hogy a visszakövetés mellőzése hogyan javítja a teljesítményt beágyazott kvantitátorok használatakor. A normál kifejezésmotor számára szükséges időt méri annak megállapításához, hogy egy bemeneti sztring nem egyezik-e két reguláris kifejezéssel. Az első reguláris kifejezés visszakövetéssel próbál egy olyan sztringet létrehozni, amely egy vagy több hexadecimális számjegy egy vagy több előfordulását tartalmazza, majd egy kettőspontot, majd egy vagy több hexadecimális számjegyet, majd két kettőspontot. A második reguláris kifejezés megegyezik az első kifejezéssel, azzal a kivételével, hogy letiltja a visszakövetést. Ahogy a példa kimenete is mutatja, a teljesítménybeli javulás jelentős a visszakövetés letiltásával kapcsolatban.

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example4
{
    public static void Run()
    {
        string input = "b51:4:1DB:9EE1:5:27d60:f44:D4:cd:E:5:0A5:4a:D24:41Ad:";
        bool matched;
        Stopwatch sw;

        Console.WriteLine("With backtracking:");
        string backPattern = "^(([0-9a-fA-F]{1,4}:)*([0-9a-fA-F]{1,4}))*(::)$";
        sw = Stopwatch.StartNew();
        matched = Regex.IsMatch(input, backPattern);
        sw.Stop();
        Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, backPattern), sw.Elapsed);
        Console.WriteLine();

        Console.WriteLine("Without backtracking:");
        string noBackPattern = "^((?>[0-9a-fA-F]{1,4}:)*(?>[0-9a-fA-F]{1,4}))*(::)$";
        sw = Stopwatch.StartNew();
        matched = Regex.IsMatch(input, noBackPattern);
        sw.Stop();
        Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, noBackPattern), sw.Elapsed);
    }
}
// The example displays output like the following:
//       With backtracking:
//       Match: False in 00:00:27.4282019
//
//       Without backtracking:
//       Match: False in 00:00:00.0001391
Imports System.Text.RegularExpressions

Module Example4
    Public Sub Run()
        Dim input As String = "b51:4:1DB:9EE1:5:27d60:f44:D4:cd:E:5:0A5:4a:D24:41Ad:"
        Dim matched As Boolean
        Dim sw As Stopwatch

        Console.WriteLine("With backtracking:")
        Dim backPattern As String = "^(([0-9a-fA-F]{1,4}:)*([0-9a-fA-F]{1,4}))*(::)$"
        sw = Stopwatch.StartNew()
        matched = Regex.IsMatch(input, backPattern)
        sw.Stop()
        Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, backPattern), sw.Elapsed)
        Console.WriteLine()

        Console.WriteLine("Without backtracking:")
        Dim noBackPattern As String = "^((?>[0-9a-fA-F]{1,4}:)*(?>[0-9a-fA-F]{1,4}))*(::)$"
        sw = Stopwatch.StartNew()
        matched = Regex.IsMatch(input, noBackPattern)
        sw.Stop()
        Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, noBackPattern), sw.Elapsed)
    End Sub
End Module
' The example displays the following output:
'       With backtracking:
'       Match: False in 00:00:27.4282019
'       
'       Without backtracking:
'       Match: False in 00:00:00.0001391

Lookbehind-állítások

A .NET két nyelvi elemet tartalmaz, (?<=a subexpressiont) és (?<!a subexpressiont), amelyek megfelelnek a bemeneti sztring előző karakterének vagy karaktereinek. Mindkét nyelvi elem nulla szélességű állítás; vagyis azt határozzák meg, hogy az aktuális karaktert közvetlenül megelőző karakter vagy karakterek helyettesíthetők-e alexpresszióval, előre- vagy visszalépés nélkül.

(?<=az alexpresszió) egy pozitív lookbehind-állítás; vagyis az aktuális pozíció előtti karakternek vagy karaktereknek meg kell egyeznie a szubexpresszióval. (?<!az alexpresszió) negatív lookbehind-állítás; vagyis az aktuális pozíció előtti karakter vagy karakterek nem egyeznek meg az alexpresszióval. Mind a pozitív, mind a negatív lookbehind állítások akkor hasznosak, ha a szubexpresszió az előző alexpresszió részhalmaza.

Az alábbi példa két egyenértékű reguláris kifejezésmintát használ, amelyek ellenőrzik a felhasználónevet egy e-mail-címben. Az első minta a túlzott visszalépés miatt gyenge teljesítménynek van kitéve. A második minta úgy módosítja az első reguláris kifejezést, hogy a beágyazott kvantálót pozitív lookbehind-állításra cseréli. A példa kimenete a metódus végrehajtási idejét Regex.IsMatch jeleníti meg.

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example5
{
    public static void Run()
    {
        Stopwatch sw;
        string input = "test@contoso.com";
        bool result;

        string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])?@";
        sw = Stopwatch.StartNew();
        result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
        sw.Stop();
        Console.WriteLine("Match: {0} in {1}", result, sw.Elapsed);

        string behindPattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@";
        sw = Stopwatch.StartNew();
        result = Regex.IsMatch(input, behindPattern, RegexOptions.IgnoreCase);
        sw.Stop();
        Console.WriteLine("Match with Lookbehind: {0} in {1}", result, sw.Elapsed);
    }
}
// The example displays output similar to the following:
//       Match: True in 00:00:00.0017549
//       Match with Lookbehind: True in 00:00:00.0000659
Module Example5
    Public Sub Run()
        Dim sw As Stopwatch
        Dim input As String = "test@contoso.com"
        Dim result As Boolean

        Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])?@"
        sw = Stopwatch.StartNew()
        result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase)
        sw.Stop()
        Console.WriteLine("Match: {0} in {1}", result, sw.Elapsed)

        Dim behindPattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@"
        sw = Stopwatch.StartNew()
        result = Regex.IsMatch(input, behindPattern, RegexOptions.IgnoreCase)
        sw.Stop()
        Console.WriteLine("Match with Lookbehind: {0} in {1}", result, sw.Elapsed)
    End Sub
End Module
' The example displays output similar to the following:
'       Match: True in 00:00:00.0017549
'       Match with Lookbehind: True in 00:00:00.0000659

Az első reguláris kifejezésminta ^[0-9A-Z]([-.\w]*[0-9A-Z])*@az alábbi táblázatban látható módon van definiálva.

Minta Leírás
^ Indítsa el az egyezést a sztring elején.
[0-9A-Z] Egyezik egy alfanumerikus karaktersel. Ez az összehasonlítás nem érzéketlen, mert a Regex.IsMatch metódust a RegexOptions.IgnoreCase beállítással hívjuk meg.
[-.\w]* Egy kötőjel, pont vagy szó karakter nulla, egy vagy több előfordulásának egyezése.
[0-9A-Z] Egyezik egy alfanumerikus karaktersel.
([-.\w]*[0-9A-Z])* A nulla vagy több kötőjel, pont vagy szó karakter kombinációjának nullával vagy több előfordulásával egyezik meg, amelyet egy alfanumerikus karakter követ. Ez az első rögzítési csoport.
@ Egyezés egy at sign ("@").

A második reguláris kifejezésminta ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@egy pozitív lookbehind-állítást használ. Az alábbi táblázatban látható módon van definiálva.

Minta Leírás
^ Indítsa el az egyezést a sztring elején.
[0-9A-Z] Egyezik egy alfanumerikus karaktersel. Ez az összehasonlítás nem érzéketlen, mert a Regex.IsMatch metódust a RegexOptions.IgnoreCase beállítással hívjuk meg.
[-.\w]* Egy kötőjel, pont vagy szó karakter nulla vagy több előfordulásának egyezése.
(?<=[0-9A-Z]) Tekintse vissza az utolsó egyező karaktert, és folytassa az egyezést, ha alfanumerikus. Vegye figyelembe, hogy az alfanumerikus karakterek a halmaz olyan részhalmazai, amelyek pontokból, kötőjelekből és minden szókarakterekből állnak.
@ Egyezés egy at sign ("@").

Lookahead-állítások

A .NET két nyelvi elemet tartalmaz, (?=a subexpressiont) és (?!a subexpressiont), amelyek megfelelnek a bemeneti sztring következő karakterének vagy karaktereinek. Mindkét nyelvi elem nulla szélességű állítás; vagyis azt határozzák meg, hogy az aktuális karaktert közvetlenül követő karakter vagy karakterek helyettesíthetők-e alexpresszióval, előre- vagy visszalépés nélkül.

(?=az alexpresszió) egy pozitív lookahead-állítás; vagyis az aktuális pozíció utáni karakternek vagy karaktereknek meg kell egyeznie az alexpresszióval. (?!Az alexpresszió) negatív kifejezés, vagyis az aktuális pozíció utáni karakter vagy karakterek nem egyezhetnek meg a szubexpresszióval. Mind a pozitív, mind a negatív lookahead-állítások akkor hasznosak, ha az alexpresszió a következő alexpresszió részhalmaza.

Az alábbi példa két egyenértékű reguláris kifejezésmintát használ, amelyek egy teljesen minősített típusnevet érvényesítenek. Az első minta a túlzott visszalépés miatt gyenge teljesítménynek van kitéve. A második módosítja az első reguláris kifejezést úgy, hogy egy beágyazott kvantálót pozitív kifejezésre cserél. A példa kimenete a metódus végrehajtási idejét Regex.IsMatch jeleníti meg.

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example6
{
    public static void Run()
    {
        string input = "aaaaaaaaaaaaaaaaaaaaaa.";
        bool result;
        Stopwatch sw;

        string pattern = @"^(([A-Z]\w*)+\.)*[A-Z]\w*$";
        sw = Stopwatch.StartNew();
        result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
        sw.Stop();
        Console.WriteLine("{0} in {1}", result, sw.Elapsed);

        string aheadPattern = @"^((?=[A-Z])\w+\.)*[A-Z]\w*$";
        sw = Stopwatch.StartNew();
        result = Regex.IsMatch(input, aheadPattern, RegexOptions.IgnoreCase);
        sw.Stop();
        Console.WriteLine("{0} in {1}", result, sw.Elapsed);
    }
}
// The example displays the following output:
//       False in 00:00:03.8003793
//       False in 00:00:00.0000866
Imports System.Text.RegularExpressions

Module Example6
    Public Sub Run()
        Dim input As String = "aaaaaaaaaaaaaaaaaaaaaa."
        Dim result As Boolean
        Dim sw As Stopwatch

        Dim pattern As String = "^(([A-Z]\w*)+\.)*[A-Z]\w*$"
        sw = Stopwatch.StartNew()
        result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase)
        sw.Stop()
        Console.WriteLine("{0} in {1}", result, sw.Elapsed)

        Dim aheadPattern As String = "^((?=[A-Z])\w+\.)*[A-Z]\w*$"
        sw = Stopwatch.StartNew()
        result = Regex.IsMatch(input, aheadPattern, RegexOptions.IgnoreCase)
        sw.Stop()
        Console.WriteLine("{0} in {1}", result, sw.Elapsed)
    End Sub
End Module
' The example displays the following output:
'       False in 00:00:03.8003793
'       False in 00:00:00.0000866

Az első reguláris kifejezésminta ^(([A-Z]\w*)+\.)*[A-Z]\w*$az alábbi táblázatban látható módon van definiálva.

Minta Leírás
^ Indítsa el az egyezést a sztring elején.
([A-Z]\w*)+\. Egy betűrendes karakter (A-Z) és egy vagy több szó karakterének egy vagy több betűjele, majd egy pont. Ez az összehasonlítás nem érzéketlen, mert a Regex.IsMatch metódust a RegexOptions.IgnoreCase beállítással hívjuk meg.
(([A-Z]\w*)+\.)* Egyezzen az előző minta nullával vagy több alkalommal.
[A-Z]\w* Betűrendes karakter, amelyet nulla vagy több szó karakter követ.
$ Az egyezés befejezése a bemeneti sztring végén.

A második reguláris kifejezésminta ^((?=[A-Z])\w+\.)*[A-Z]\w*$egy pozitív lookahead-állítást használ. Az alábbi táblázatban látható módon van definiálva.

Minta Leírás
^ Indítsa el az egyezést a sztring elején.
(?=[A-Z]) Nézze meg az első karaktert, és folytassa az egyezést, ha betűrendes (A-Z). Ez az összehasonlítás nem érzéketlen, mert a Regex.IsMatch metódust a RegexOptions.IgnoreCase beállítással hívjuk meg.
\w+\. Egy vagy több szó karakterének egy ponttal való egyeztetése.
((?=[A-Z])\w+\.)* Egy vagy több szókarakterek mintájának felel meg, amelyeket egy nulla vagy több pont követ. A kezdő szó karakterének betűrendesnek kell lennie.
[A-Z]\w* Betűrendes karakter, amelyet nulla vagy több szó karakter követ.
$ Az egyezés befejezése a bemeneti sztring végén.

Általános teljesítménnyel kapcsolatos szempontok

Az alábbi javaslatok nem kifejezetten a túlzott visszakövetés megelőzésére irányulnak, de segíthetnek a normál kifejezés teljesítményének növelésében:

  1. Precompile erősen használt minták. Ennek legjobb módja, ha a reguláris kifejezésforrás-generátort használja az előfordításhoz. Ha a forrásgenerátor nem érhető el az alkalmazáshoz, például nem a .NET 7-et vagy újabb verziót célozza, vagy fordításkor nem ismeri a mintát, használja a RegexOptions.Compiled lehetőséget.

  2. Nagymértékben használt Regex-objektumok gyorsítótárazása. Ez implicit módon akkor fordul elő, ha a forrásgenerátort használja. Ellenkező esetben hozzon létre egy Regex-objektumot, és a statikus Regex-metódusok használata helyett, vagy egy Regex-objektum létrehozása és eldobása helyett tárolja újra.

  3. Kezdje el az egyezést egy eltolásból. Ha tudja, hogy az egyezések mindig egy bizonyos eltoláson túl kezdődnek a mintába, adja át az eltolást túlterheléssel, például Regex.Match(String, Int32). Ez csökkenti a motor által figyelembe veendő szöveg mennyiségét.

  4. Csak a szükséges információkat gyűjtse össze. Ha csak azt kell tudnia, hogy egyezés történik-e, de nem ott, ahol az egyezés történik, akkor inkább.Regex.IsMatch Ha csak azt kell tudnia, hogy hányszor egyezik valami, használja inkább a .Regex.Count Ha csak az egyezések határait kell ismernie, de semmit sem a találatok rögzítéséről, használja inkább a .Regex.EnumerateMatches Minél kevesebb információt kell nyújtania a motornak, annál jobb.

  5. Kerülje a szükségtelen rögzítéseket. A mintában szereplő zárójelek alapértelmezés szerint rögzítési csoportot alkotnak. Ha nincs szüksége rögzítésekre, adjon meg RegexOptions.ExplicitCapture vagy használjon nem rögzített csoportokat . Ez menti a motort, hogy nyomon kövesse ezeket a rögzítéseket.

Lásd még