溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

.NET正則表達(dá)式最佳的使用方法是什么

發(fā)布時間:2022-02-07 15:08:54 來源:億速云 閱讀:125 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“.NET正則表達(dá)式最佳的使用方法是什么”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“.NET正則表達(dá)式最佳的使用方法是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

    .NET 中的正則表達(dá)式引擎是一種功能強(qiáng)大而齊全的工具,它基于模式匹配(而不是比較和匹配文本)來處理文本。 在大多數(shù)情況下,它可以快速、高效地執(zhí)行模式匹配。 但在某些情況下,正則表達(dá)式引擎的速度似乎很慢。 在極端情況下,它甚至看似停止響應(yīng),因?yàn)樗鼤萌舾蓚€小時甚至若干天處理相對小的輸入。

    本主題概述開發(fā)人員為了確保其正則表達(dá)式實(shí)現(xiàn)最佳性能可以采納的一些最佳做法。

    .NET正則表達(dá)式最佳的使用方法是什么

    考慮輸入源

    通常,正則表達(dá)式可接受兩種類型的輸入:受約束的輸入或不受約束的輸入。 受約束的輸入是源自已知或可靠的源并遵循預(yù)定義格式的文本。 不受約束的輸入是源自不可靠的源(如 Web 用戶)并且可能不遵循預(yù)定義或預(yù)期格式的文本。

    編寫的正則表達(dá)式模式的目的通常是匹配有效輸入。 也就是說,開發(fā)人員檢查他們要匹配的文本,然后編寫與其匹配的正則表達(dá)式模式。 然后,開發(fā)人員使用多個有效輸入項(xiàng)進(jìn)行測試,以確定此模式是否需要更正或進(jìn)一步細(xì)化。 當(dāng)模式可匹配所有假定的有效輸入時,則將其聲明為生產(chǎn)就緒并且可包括在發(fā)布的應(yīng)用程序中。 這使得正則表達(dá)式模式適合匹配受約束的輸入。 但它不適合匹配不受約束的輸入。

    若要匹配不受約束的輸入,正則表達(dá)式必須能夠高效處理以下三種文本:

    • 與正則表達(dá)式模式匹配的文本。

    • 與正則表達(dá)式模式不匹配的文本。

    • 與正則表達(dá)式模式大致匹配的文本。

    對于為了處理受約束的輸入而編寫的正則表達(dá)式,最后一種文本類型尤其存在問題。 如果該正則表達(dá)式還依賴大量回溯,則正則表達(dá)式引擎可能會花費(fèi)大量時間(在有些情況下,需要許多個小時或許多天)來處理看似無害的文本。

    .NET正則表達(dá)式最佳的使用方法是什么

    例如,考慮一種很常用但很有問題的用于驗(yàn)證電子郵件地址別名的正則表達(dá)式。 編寫正則表達(dá)式 ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ 的目的是處理被視為有效的電子郵件地址,該地址包含一個字母數(shù)字字符,后跟零個或多個可為字母數(shù)字、句點(diǎn)或連字符的字符。 該正則表達(dá)式必須以字母數(shù)字字符結(jié)束。 但正如下面的示例所示,盡管此正則表達(dá)式可以輕松處理有效輸入,但在處理接近有效的輸入時性能非常低效。

    using System;
    using System.Diagnostics;
    using System.Text.RegularExpressions;
    
    public class Example
    {
       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

    如該示例輸出所示,正則表達(dá)式引擎處理有效電子郵件別名的時間間隔大致相同,與其長度無關(guān)。 另一方面,當(dāng)接近有效的電子郵件地址包含五個以上字符時,字符串中每增加一個字符,處理時間會大約增加一倍。 這意味著,處理接近有效的 28 個字符構(gòu)成的字符串將需要一個小時,處理接近有效的 33 個字符構(gòu)成的字符串將需要接近一天的時間。

    由于開發(fā)此正則表達(dá)式時只考慮了要匹配的輸入的格式,因此未能考慮與模式不匹配的輸入。 這反過來會使與正則表達(dá)式模式近似匹配的不受約束輸入的性能顯著降低。

    若要解決此問題,可執(zhí)行下列操作:

    • 開發(fā)模式時,應(yīng)考慮回溯對正則表達(dá)式引擎的性能的影響程度,特別是當(dāng)正則表達(dá)式設(shè)計(jì)用于處理不受約束的輸入時。 有關(guān)詳細(xì)信息,請參閱控制回溯部分。

    • 使用無效輸入、接近有效的輸入以及有效輸入對正則表達(dá)式進(jìn)行完全測試。 若要為特定正則表達(dá)式隨機(jī)生成輸入,可以使用 Rex,這是 Microsoft Research 提供的正則表達(dá)式探索工具。

    適當(dāng)處理對象實(shí)例化

    .NET 正則表達(dá)式對象模型的核心是 xref:System.Text.RegularExpressions.Regex?displayProperty=nameWithType 類,表示正則表達(dá)式引擎。 通常,影響正則表達(dá)式性能的單個最大因素是 xref:System.Text.RegularExpressions.Regex 引擎的使用方式。 定義正則表達(dá)式需要將正則表達(dá)式引擎與正則表達(dá)式模式緊密耦合。 無論該耦合過程是需要通過向其構(gòu)造函數(shù)傳遞正則表達(dá)式模式來實(shí)例化 xref:System.Text.RegularExpressions.Regex 還是通過向其傳遞正則表達(dá)式模式和要分析的字符串來調(diào)用靜態(tài)方法,都必然會消耗大量資源。

    .NET正則表達(dá)式最佳的使用方法是什么

    可將正則表達(dá)式引擎與特定正則表達(dá)式模式耦合,然后使用該引擎以若干種方式匹配文本:

    • 可以調(diào)用靜態(tài)模式匹配方法,如 xref:System.Text.RegularExpressions.Regex.Match(System.String%2CSystem.String)?displayProperty=nameWithType。 這不需要實(shí)例化正則表達(dá)式對象。

    • 可以實(shí)例化一個 xref:System.Text.RegularExpressions.Regex 對象并調(diào)用已解釋的正則表達(dá)式的實(shí)例模式匹配方法。 這是將正則表達(dá)式引擎綁定到正則表達(dá)式模式的默認(rèn)方法。 如果實(shí)例化 xref:System.Text.RegularExpressions.Regex 對象時未使用包括 options 標(biāo)記的 xref:System.Text.RegularExpressions.RegexOptions.Compiled 自變量,則會生成此方法。

    • 可以實(shí)例化一個 xref:System.Text.RegularExpressions.Regex 對象并調(diào)用已編譯的正則表達(dá)式的實(shí)例模式匹配方法。 當(dāng)使用包括 xref:System.Text.RegularExpressions.Regex 標(biāo)記的 options 參數(shù)實(shí)例化 xref:System.Text.RegularExpressions.RegexOptions.Compiled 對象時,正則表達(dá)式對象表示已編譯的模式。

    • 可以創(chuàng)建一個與特定正則表達(dá)式模式緊密耦合的特殊用途的 xref:System.Text.RegularExpressions.Regex 對象,編譯該對象,并將其保存到獨(dú)立程序集中。 為此,可調(diào)用 xref:System.Text.RegularExpressions.Regex.CompileToAssembly*?displayProperty=nameWithType 方法。

    這種調(diào)用正則表達(dá)式匹配方法的特殊方式會對應(yīng)用程序產(chǎn)生顯著影響。 以下各節(jié)討論何時使用靜態(tài)方法調(diào)用、已解釋的正則表達(dá)式和已編譯的正則表達(dá)式,以改進(jìn)應(yīng)用程序的性能。

    .NET正則表達(dá)式最佳的使用方法是什么

    靜態(tài)正則表達(dá)式

    建議將靜態(tài)正則表達(dá)式方法用作使用同一正則表達(dá)式重復(fù)實(shí)例化正則表達(dá)式對象的替代方法。 與正則表達(dá)式對象使用的正則表達(dá)式模式不同,靜態(tài)方法調(diào)用所使用的模式中的操作代碼或已編譯的 Microsoft 中間語言 (MSIL) 由正則表達(dá)式引擎緩存在內(nèi)部。

    例如,事件處理程序會頻繁調(diào)用其他方法來驗(yàn)證用戶輸入。 下面的代碼中反映了這一點(diǎn),其中一個 xref:System.Windows.Forms.Button 控件的 xref:System.Windows.Forms.Control.Click 事件用于調(diào)用名為 IsValidCurrency 的方法,該方法檢查用戶是否輸入了后跟至少一個十進(jìn)制數(shù)的貨幣符號。

    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.";
    }

    下面的示例顯示 IsValidCurrency 方法的一個非常低效的實(shí)現(xiàn)。 請注意,每個方法調(diào)用使用相同模式重新實(shí)例化 xref:System.Text.RegularExpressions.Regex 對象。 這反過來意味著,每次調(diào)用該方法時,都必須重新編譯正則表達(dá)式模式。

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

    應(yīng)將此低效代碼替換為對靜態(tài) xref:System.Text.RegularExpressions.Regex.IsMatch(System.String%2CSystem.String)?displayProperty=nameWithType 方法的調(diào)用。 這樣便不必在你每次要調(diào)用模式匹配方法時都實(shí)例化 xref:System.Text.RegularExpressions.Regex 對象,還允許正則表達(dá)式引擎從其緩存中檢索正則表達(dá)式的已編譯版本。

    using System;
    using System.Text.RegularExpressions;
    
    public class RegexLib
    {
       public static bool IsValidCurrency(string currencyValue)
       {
          string pattern = @"\p{Sc}+\s*\d+";
          return Regex.IsMatch(currencyValue, pattern);
       }
    }

    默認(rèn)情況下,將緩存最后 15 個最近使用的靜態(tài)正則表達(dá)式模式。 對于需要大量已緩存的靜態(tài)正則表達(dá)式的應(yīng)用程序,可通過設(shè)置 Regex.CacheSize 屬性來調(diào)整緩存大小。

    此示例中使用的正則表達(dá)式 \p{Sc}+\s*\d+ 可驗(yàn)證輸入字符串是否包含一個貨幣符號和至少一個十進(jìn)制數(shù)。 模式的定義如下表所示。

    模式描述
    \p{Sc}+與 Unicode 符號、貨幣類別中的一個或多個字符匹配。
    \s*匹配零個或多個空白字符。
    \d+匹配一個或多個十進(jìn)制數(shù)字。

    已解釋與已編譯的正則表達(dá)式

    將解釋未通過 RegexOptions.Compiled 選項(xiàng)的規(guī)范綁定到正則表達(dá)式引擎的正則表達(dá)式模式。 在實(shí)例化正則表達(dá)式對象時,正則表達(dá)式引擎會將正則表達(dá)式轉(zhuǎn)換為一組操作代碼。 調(diào)用實(shí)例方法時,操作代碼會轉(zhuǎn)換為 MSIL 并由 JIT 編譯器執(zhí)行。 同樣,當(dāng)調(diào)用一種靜態(tài)正則表達(dá)式方法并且在緩存中找不到該正則表達(dá)式時,正則表達(dá)式引擎會將該正則表達(dá)式轉(zhuǎn)換為一組操作代碼并將其存儲在緩存中。 然后,它將這些操作代碼轉(zhuǎn)換為 MSIL,以便于 JIT 編譯器執(zhí)行。 已解釋的正則表達(dá)式會減少啟動時間,但會使執(zhí)行速度變慢。 因此,在少數(shù)方法調(diào)用中使用正則表達(dá)式時或調(diào)用正則表達(dá)式方法的確切數(shù)量未知但預(yù)期很小時,使用已解釋的正則表達(dá)式的效果最佳。 隨著方法調(diào)用數(shù)量的增加,執(zhí)行速度變慢對性能的影響會超過減少啟動時間帶來的性能改進(jìn)。

    將編譯通過 RegexOptions.Compiled 選項(xiàng)的規(guī)范綁定到正則表達(dá)式引擎的正則表達(dá)式模式。 這意味著,當(dāng)實(shí)例化正則表達(dá)式對象時或當(dāng)調(diào)用一種靜態(tài)正則表達(dá)式方法并且在緩存中找不到該正則表達(dá)式時,正則表達(dá)式引擎會將該正則表達(dá)式轉(zhuǎn)換為一組中間操作代碼,這些代碼之后會轉(zhuǎn)換為 MSIL。 調(diào)用方法時,JIT 編譯器將執(zhí)行該 MSIL。 與已解釋的正則表達(dá)式相比,已編譯的正則表達(dá)式增加了啟動時間,但執(zhí)行各種模式匹配方法的速度更快。 因此,相對于調(diào)用的正則表達(dá)式方法的數(shù)量,因編譯正則表達(dá)式而產(chǎn)生的性能產(chǎn)生了改進(jìn)。

    簡言之,當(dāng)你使用特定正則表達(dá)式調(diào)用正則表達(dá)式方法相對不頻繁時,建議使用已解釋的正則表達(dá)式。 當(dāng)你使用特定正則表達(dá)式調(diào)用正則表達(dá)式方法相對頻繁時,應(yīng)使用已編譯的正則表達(dá)式。 很難確定已解釋的正則表達(dá)式執(zhí)行速度減慢超出啟動時間減少帶來的性能增益的確切閾值,或已編譯的正則表達(dá)式啟動速度減慢超出執(zhí)行速度加快帶來的性能增益的閾值。 這依賴于各種因素,包括正則表達(dá)式的復(fù)雜程度和它處理的特定數(shù)據(jù)。 若要確定已解釋或已編譯的正則表達(dá)式是否可為特定應(yīng)用程序方案提供最佳性能,可以使用 Diagnostics.Stopwatch 類來比較其執(zhí)行時間。

    下面的示例比較了已編譯和已解釋正則表達(dá)式在讀取 Theodore Dreiser 所著《金融家》中前十句文本和所有句文本時的性能。 如示例輸出所示,當(dāng)只對匹配方法的正則表達(dá)式進(jìn)行十次調(diào)用時,已解釋的正則表達(dá)式與已編譯的正則表達(dá)式相比,可提供更好的性能。 但是,當(dāng)進(jìn)行大量調(diào)用(在此示例中,超過 13,000 次調(diào)用)時,已編譯的正則表達(dá)式可提供更好的性能。

    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Text.RegularExpressions;
    
    public class Example
    {
       public static void Main()
       {
          string pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
          Stopwatch sw;
          Match match;
          int ctr;
    
          StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");
          string input = inFile.ReadToEnd();
          inFile.Close();
    
          // Read first ten sentences with interpreted regex.
          Console.WriteLine("10 Sentences with Interpreted Regex:");
          sw = Stopwatch.StartNew();
          Regex int10 = new Regex(pattern, RegexOptions.Singleline);
          match = int10.Match(input);
          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(input);
          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 Regex(pattern, RegexOptions.Singleline);
          match = intAll.Match(input);
          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 Regex(pattern,
                          RegexOptions.Singleline | RegexOptions.Compiled);
          match = compAll.Match(input);
          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);
       }
    }
    // The example displays the following output:
    //       10 Sentences with Interpreted Regex:
    //          10 matches in 00:00:00.0047491
    //       10 Sentences with Compiled Regex:
    //          10 matches in 00:00:00.0141872
    //       All Sentences with Interpreted Regex:
    //          13,443 matches in 00:00:01.1929928
    //       All Sentences with Compiled Regex:
    //          13,443 matches in 00:00:00.7635869
    //
    //       >compare1
    //       10 Sentences with Interpreted Regex:
    //          10 matches in 00:00:00.0046914
    //       10 Sentences with Compiled Regex:
    //          10 matches in 00:00:00.0143727
    //       All Sentences with Interpreted Regex:
    //          13,443 matches in 00:00:01.1514100
    //       All Sentences with Compiled Regex:
    //          13,443 matches in 00:00:00.7432921

    該示例中使用的正則表達(dá)式模式 \b(\w+((\r?\n)|,?\s))*\w+[.?:;!] 的定義如下表所示。

    模式描述
    \b在單詞邊界處開始匹配。
    \w+匹配一個或多個單詞字符。
    (\r?\n)|,?\s)匹配零個或一個回車符后跟一個換行符,或零個或一個逗號后跟一個空白字符。
    (\w+((\r?\n)|,?\s))*匹配一個或多個單詞字符的零個或多個事例,后跟零個或一個回車符和換行符,或后跟零個或一個逗號、一個空格字符。
    \w+匹配一個或多個單詞字符。
    [.?:;!]匹配句號、問號、冒號、分號或感嘆號。

    正則表達(dá)式:編譯為程序集

    借助 .NET,還可以創(chuàng)建包含已編譯正則表達(dá)式的程序集。 這樣會將正則表達(dá)式編譯對性能造成的影響從運(yùn)行時轉(zhuǎn)移到設(shè)計(jì)時。 但是,這還涉及一些其他工作:必須提前定義正則表達(dá)式并將其編譯為程序集。 然后,編譯器在編譯使用該程序集的正則表達(dá)式的源代碼時,可以引用此程序集。 程序集內(nèi)的每個已編譯正則表達(dá)式都由從 xref:System.Text.RegularExpressions.Regex 派生的類來表示。

    若要將正則表達(dá)式編譯為程序集,可調(diào)用 Regex.CompileToAssembly(RegexCompilationInfo[], AssemblyName) 方法并向其傳遞表示要編譯的正則表達(dá)式的 RegexCompilationInfo 對象數(shù)組和包含有關(guān)要創(chuàng)建的程序集的信息的 AssemblyName 對象。

    建議你在以下情況下將正則表達(dá)式編譯為程序集:

    • 如果你是要創(chuàng)建可重用正則表達(dá)式庫的組件開發(fā)人員。

    • 如果你預(yù)期正則表達(dá)式的模式匹配方法要被調(diào)用的次數(shù)無法確定 -- 從任意位置,次數(shù)可能為一次兩次到上千上萬次。 與已編譯或已解釋的正則表達(dá)式不同,編譯為單獨(dú)程序集的正則表達(dá)式可提供與方法調(diào)用數(shù)量無關(guān)的一致性能。

    如果使用已編譯的正則表達(dá)式來優(yōu)化性能,則不應(yīng)使用反射來創(chuàng)建程序集,加載正則表達(dá)式引擎并執(zhí)行其模式匹配方法。 這要求你避免動態(tài)生成正則表達(dá)式模式,并且要在創(chuàng)建程序集時指定模式匹配選項(xiàng)(如不區(qū)分大小寫的模式匹配)。 它還要求將創(chuàng)建程序集的代碼與使用正則表達(dá)式的代碼分離。

    下面的示例演示如何創(chuàng)建包含已編譯的正則表達(dá)式的程序集。 它創(chuàng)建包含一個正則表達(dá)式類 SentencePattern 的程序集 RegexLib.dll,其中包含已解釋與已編譯的正則表達(dá)式部分中使用的句子匹配的正則表達(dá)式模式。

    using System;
    using System.Reflection;
    using System.Text.RegularExpressions;
    
    public class Example
    {
       public static void Main()
       {
          RegexCompilationInfo SentencePattern =
                               new RegexCompilationInfo(@"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
                                                        RegexOptions.Multiline,
                                                        "SentencePattern",
                                                        "Utilities.RegularExpressions",
                                                        true);
          RegexCompilationInfo[] regexes = { SentencePattern };
          AssemblyName assemName = new AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null");
          Regex.CompileToAssembly(regexes, assemName);
       }
    }

    在將示例編譯為可執(zhí)行文件并運(yùn)行時,它會創(chuàng)建一個名為 RegexLib.dll 的程序集。 正則表達(dá)式用名為 Utilities.RegularExpressions.SentencePattern 并由 RegularExpressions.Regex 派生的類來表示。 然后,下面的示例使用已編譯正則表達(dá)式,從 Theodore Dreiser 所著《金融家》文本中提取句子。

    using System;
    using System.IO;
    using System.Text.RegularExpressions;
    using Utilities.RegularExpressions;
    
    public class Example
    {
       public static void Main()
       {
          SentencePattern pattern = new SentencePattern();
          StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");
          string input = inFile.ReadToEnd();
          inFile.Close();
    
          MatchCollection matches = pattern.Matches(input);
          Console.WriteLine("Found {0:N0} sentences.", matches.Count);
       }
    }
    // The example displays the following output:
    //      Found 13,443 sentences.

    控制回溯

    通常,正則表達(dá)式引擎使用線性進(jìn)度在輸入字符串中移動并將其編譯為正則表達(dá)式模式。 但是,當(dāng)在正則表達(dá)式模式中使用不確定限定符(如 *、+ 和 ?)時,正則表達(dá)式引擎可能會放棄一部分成功的分部匹配,并返回以前保存的狀態(tài),以便為整個模式搜索成功匹配。 此過程稱為回溯。

    .NET正則表達(dá)式最佳的使用方法是什么

    支持回溯可為正則表達(dá)式提供強(qiáng)大的功能和靈活性。 還可將控制正則表達(dá)式引擎操作的職責(zé)交給正則表達(dá)式開發(fā)人員來處理。 由于開發(fā)人員通常不了解此職責(zé),因此其誤用回溯或依賴過多回溯通常會顯著降低正則表達(dá)式的性能。 在最糟糕的情況下,輸入字符串中每增加一個字符,執(zhí)行時間會加倍。 實(shí)際上,如果過多使用回溯,則在輸入與正則表達(dá)式模式近似匹配時很容易創(chuàng)建無限循環(huán)的編程等效形式;正則表達(dá)式引擎可能需要幾小時甚至幾天來處理相對短的輸入字符串。

    通常,盡管回溯不是匹配所必需的,但應(yīng)用程序會因使用回溯而對性能產(chǎn)生負(fù)面影響。 例如,正則表達(dá)式 \b\p{Lu}\w*\b 將匹配以大寫字符開頭的所有單詞,如下表所示。

    模式描述
    \b在單詞邊界處開始匹配。
    \p{Lu}匹配大寫字符。
    \w*匹配零個或多個單詞字符。
    \b在單詞邊界處結(jié)束匹配。

    由于單詞邊界與單詞字符不同也不是其子集,因此正則表達(dá)式引擎在匹配單詞字符時無法跨越單詞邊界。 這意味著,對于此正則表達(dá)式而言,回溯對任何匹配的總體成功不會有任何貢獻(xiàn) -- 由于正則表達(dá)式引擎被強(qiáng)制為單詞字符的每個成功的初步匹配保存其狀態(tài),因此它只會降低性能。

    如果確定不需要回溯,可使用 (?>subexpression) 語言元素(被稱為原子組)來禁用它。 下面的示例通過使用兩個正則表達(dá)式來分析輸入字符串。 第一個正則表達(dá)式 \b\p{Lu}\w*\b 依賴于回溯。 第二個正則表達(dá)式 \b\p{Lu}(?>\w*)\b 禁用回溯。 如示例輸出所示,這兩個正則表達(dá)式產(chǎn)生的結(jié)果相同。

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
       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

    在許多情況下,在將正則表達(dá)式模式與輸入文本匹配時,回溯很重要。 但是,過度回溯會嚴(yán)重降低性能,并且會產(chǎn)生應(yīng)用程序已停止響應(yīng)的感覺。 特別需要指出的是,當(dāng)嵌套限定符并且與外部子表達(dá)式匹配的文本為與內(nèi)部子表達(dá)式匹配的文本的子集時,尤其會出現(xiàn)這種情況。

    .NET正則表達(dá)式最佳的使用方法是什么

    例如,正則表達(dá)式模式 ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ 用于匹配至少包括一個字母數(shù)字字符的部件號。 任何附加字符可以包含字母數(shù)字字符、連字符、下劃線或句號,但最后一個字符必須為字母數(shù)字。 美元符號用于終止部件號。 在某些情況下,由于限定符嵌套并且子表達(dá)式 [0-9A-Z] 是子表達(dá)式 [-.\w]* 的子集,因此此正則表達(dá)式模式會表現(xiàn)出極差的性能。

    在這些情況下,可通過移除嵌套限定符并將外部子表達(dá)式替換為零寬度預(yù)測先行和回顧斷言來優(yōu)化正則表達(dá)式性能。 預(yù)測先行和回顧斷言是定位點(diǎn);它們不在輸入字符串中移動指針,而是通過預(yù)測先行或回顧來檢查是否滿足指定條件。 例如,可將部件號正則表達(dá)式重寫為 ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$。 此正則表達(dá)式模式的定義如下表所示。

    模式描述
    ^從輸入字符串的開頭部分開始匹配。
    [0-9A-Z]匹配字母數(shù)字字符。 部件號至少要包含此字符。
    [-.\w]*匹配零個或多個任意單詞字符、連字符或句號。
    \$匹配美元符號。
    (?<=[0-9A-Z])查看作為結(jié)束的美元符號,以確保前一個字符是字母數(shù)字。
    $在輸入字符串末尾結(jié)束匹配。

    下面的示例演示了如何使用此正則表達(dá)式來匹配包含可能部件號的數(shù)組。

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
       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.

    .NET 中的正則表達(dá)式語言包括以下可用于消除嵌套限定符的語言元素。 有關(guān)詳細(xì)信息,請參閱分組構(gòu)造。

    語言元素描述
    (?= subexpression )零寬度正預(yù)測先行。 預(yù)測先行當(dāng)前位置,以確定 subexpression 是否與輸入字符串匹配。
    (?! subexpression )零寬度負(fù)預(yù)測先行。 預(yù)測先行當(dāng)前位置,以確定 subexpression 是否不與輸入字符串匹配。
    (?<= subexpression )零寬度正回顧。 回顧后發(fā)當(dāng)前位置,以確定 subexpression 是否與輸入字符串匹配。
    (?<! subexpression )零寬度負(fù)回顧。 回顧后發(fā)當(dāng)前位置,以確定 subexpression 是否不與輸入字符串匹配。

    使用超時值

    如果正則表達(dá)式處理與正則表達(dá)式模式大致匹配的輸入,則通常依賴于會嚴(yán)重影響其性能的過度回溯。 除認(rèn)真考慮對回溯的使用以及針對大致匹配輸入對正則表達(dá)式進(jìn)行測試之外,還應(yīng)始終設(shè)置一個超時值以確保最大程度地降低過度回溯的影響(如果有)。

    正則表達(dá)式超時間隔定義了在超時前正則表達(dá)式引擎用于查找單個匹配項(xiàng)的時間長度。默認(rèn)超時間隔為 Regex.InfiniteMatchTimeout,這意味著正則表達(dá)式不會超時??梢园慈缦滤局貙懘酥挡⒍x超時間隔:

    • 在實(shí)例化一個 Regex 對象(通過調(diào)用 Regex(String, RegexOptions, TimeSpan) 構(gòu)造函數(shù))時,提供一個超時值。

    • 調(diào)用靜態(tài)模式匹配方法,如 Regex.Match(String, String, RegexOptions, TimeSpan) 或 Regex.Replace(String, String, String, RegexOptions, TimeSpan),其中包含 matchTimeout 參數(shù)。

    • 對于通過調(diào)用 Regex.CompileToAssembly 方法創(chuàng)建的已編譯的正則表達(dá)式,可調(diào)用帶有 TimeSpan 類型的參數(shù)的構(gòu)造函數(shù)。

    如果定義了超時間隔并且在此間隔結(jié)束時未找到匹配項(xiàng),則正則表達(dá)式方法將引發(fā) RegexMatchTimeoutException 異常。 在異常處理程序中,可以選擇使用一個更長的超時間隔來重試匹配、放棄匹配嘗試并假定沒有匹配項(xiàng),或者放棄匹配嘗試并記錄異常信息以供未來分析。

    下面的示例定義了一種 GetWordData 方法,此方法實(shí)例化了一個正則表達(dá)式,使其具有 350 毫秒的超時間隔,用于計(jì)算文本文件中的詞語數(shù)和一個詞語中的平均字符數(shù)。 如果匹配操作超時,則超時間隔將延長 350 毫秒并重新實(shí)例化 Regex 對象。 如果新的超時間隔超過 1 秒,則此方法將再次向調(diào)用方引發(fā)異常。

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text.RegularExpressions;
    
    public class Example
    {
       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);
       }
    }

    只在必要時捕獲

    .NET 中的正則表達(dá)式支持許多分組構(gòu)造,這樣,便可以將正則表達(dá)式模式分組為一個或多個子表達(dá)式。 .NET 正則表達(dá)式語言中最常用的分組構(gòu)造為 (subexpression)(用于定義編號捕獲組)和 (?subexpression)(用于定義命名捕獲組)。 分組構(gòu)造是創(chuàng)建反向引用和定義要應(yīng)用限定符的子表達(dá)式時所必需的。

    但是,使用這些語言元素會產(chǎn)生一定的開銷。 它們會導(dǎo)致用最近的未命名或已命名捕獲來填充 GroupCollection 屬性返回的 Match.Groups 對象,如果單個分組構(gòu)造已捕獲輸入字符串中的多個子字符串,則還會填充包含多個 CaptureCollection 對象的特定捕獲組的 Group.Captures 屬性返回的 Capture 對象。

    通常,只在正則表達(dá)式中使用分組構(gòu)造,這樣可對其應(yīng)用限定符,而且以后不會使用這些子表達(dá)式捕獲的組。 例如,正則表達(dá)式 \b(\w+[;,]?\s?)+[.?!] 用于捕獲整個句子。 下表描述了此正則表達(dá)式模式中的語言元素及其對 Match 對象的 Match.Groups 和 Group.Captures 集合的影響。

    模式描述
    \b在單詞邊界處開始匹配。
    \w+匹配一個或多個單詞字符。
    [;,]?匹配零個或一個逗號或分號。
    \s?匹配零個或一個空白字符。
    (\w+[;,]?\s?)+匹配以下一個或多個事例:一個或多個單詞字符,后跟一個可選逗號或分號,一個可選的空白字符。 用于定義第一個捕獲組,它是必需的,以便將重復(fù)多個單詞字符的組合(即單詞)后跟可選標(biāo)點(diǎn)符號,直至正則表達(dá)式引擎到達(dá)句子末尾。
    [.?!]匹配句號、問號或感嘆號。

    如下面的示例所示,當(dāng)找到匹配時,GroupCollection 和 CaptureCollection 對象都將用匹配中的捕獲內(nèi)容來填充。 在此情況下,存在捕獲組 (\w+[;,]?\s?),因此可對其應(yīng)用 + 限定符,從而使得正則表達(dá)式模式可與句子中的每個單詞匹配。 否則,它將匹配句子中的最后一個單詞。

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
       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.

    當(dāng)你只使用子表達(dá)式來對其應(yīng)用限定符并且你對捕獲的文本不感興趣時,應(yīng)禁用組捕獲。 例如,(?:subexpression) 語言元素可防止應(yīng)用此元素的組捕獲匹配的子字符串。 在下面的示例中,上一示例中的正則表達(dá)式模式更改為 \b(?:\w+[;,]?\s?)+[.?!]。 正如輸出所示,它禁止正則表達(dá)式引擎填充 GroupCollection 和 CaptureCollection 集合。

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
       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.

    可以通過以下方式之一來禁用捕獲:

    • 使用 (?:subexpression) 語言元素。 此元素可防止在它應(yīng)用的組中捕獲匹配的子字符串。 它不在任何嵌套的組中禁用子字符串捕獲。

    • 使用 ExplicitCapture 選項(xiàng)。 在正則表達(dá)式模式中禁用所有未命名或隱式捕獲。 使用此選項(xiàng)時,只能捕獲與使用 (?subexpression) 語言元素定義的命名組匹配的子字符串。 可將 ExplicitCapture 標(biāo)記傳遞給 options 類構(gòu)造函數(shù)的 Regex 參數(shù)或 options 靜態(tài)匹配方法的 Regex 參數(shù)。

    • 在 n 語言元素中使用 (?imnsx) 選項(xiàng)。 此選項(xiàng)將在元素出現(xiàn)的正則表達(dá)式模式中的點(diǎn)處禁用所有未命名或隱式捕獲。 捕獲將一直禁用到模式結(jié)束或 (-n) 選項(xiàng)啟用未命名或隱式捕獲。 有關(guān)詳細(xì)信息,請參閱 其他構(gòu)造。

    • 在 n 語言元素中使用 (?imnsx:subexpression) 選項(xiàng)。 此選項(xiàng)可在 subexpression 中禁用所有未命名或隱式捕獲。 同時禁用任何未命名或隱式的嵌套捕獲組進(jìn)行的任何捕獲。

    讀到這里,這篇“.NET正則表達(dá)式最佳的使用方法是什么”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點(diǎn)還需要大家自己動手實(shí)踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI