您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“.NET正則表達式最佳的使用方法是什么”,內容詳細,步驟清晰,細節處理妥當,希望這篇“.NET正則表達式最佳的使用方法是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
.NET 中的正則表達式引擎是一種功能強大而齊全的工具,它基于模式匹配(而不是比較和匹配文本)來處理文本。 在大多數情況下,它可以快速、高效地執行模式匹配。 但在某些情況下,正則表達式引擎的速度似乎很慢。 在極端情況下,它甚至看似停止響應,因為它會用若干個小時甚至若干天處理相對小的輸入。
本主題概述開發人員為了確保其正則表達式實現最佳性能可以采納的一些最佳做法。
通常,正則表達式可接受兩種類型的輸入:受約束的輸入或不受約束的輸入。 受約束的輸入是源自已知或可靠的源并遵循預定義格式的文本。 不受約束的輸入是源自不可靠的源(如 Web 用戶)并且可能不遵循預定義或預期格式的文本。
編寫的正則表達式模式的目的通常是匹配有效輸入。 也就是說,開發人員檢查他們要匹配的文本,然后編寫與其匹配的正則表達式模式。 然后,開發人員使用多個有效輸入項進行測試,以確定此模式是否需要更正或進一步細化。 當模式可匹配所有假定的有效輸入時,則將其聲明為生產就緒并且可包括在發布的應用程序中。 這使得正則表達式模式適合匹配受約束的輸入。 但它不適合匹配不受約束的輸入。
若要匹配不受約束的輸入,正則表達式必須能夠高效處理以下三種文本:
與正則表達式模式匹配的文本。
與正則表達式模式不匹配的文本。
與正則表達式模式大致匹配的文本。
對于為了處理受約束的輸入而編寫的正則表達式,最后一種文本類型尤其存在問題。 如果該正則表達式還依賴大量回溯,則正則表達式引擎可能會花費大量時間(在有些情況下,需要許多個小時或許多天)來處理看似無害的文本。
例如,考慮一種很常用但很有問題的用于驗證電子郵件地址別名的正則表達式。 編寫正則表達式 ^[0-9A-Z]([-.\w]*[0-9A-Z])*$
的目的是處理被視為有效的電子郵件地址,該地址包含一個字母數字字符,后跟零個或多個可為字母數字、句點或連字符的字符。 該正則表達式必須以字母數字字符結束。 但正如下面的示例所示,盡管此正則表達式可以輕松處理有效輸入,但在處理接近有效的輸入時性能非常低效。
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
如該示例輸出所示,正則表達式引擎處理有效電子郵件別名的時間間隔大致相同,與其長度無關。 另一方面,當接近有效的電子郵件地址包含五個以上字符時,字符串中每增加一個字符,處理時間會大約增加一倍。 這意味著,處理接近有效的 28 個字符構成的字符串將需要一個小時,處理接近有效的 33 個字符構成的字符串將需要接近一天的時間。
由于開發此正則表達式時只考慮了要匹配的輸入的格式,因此未能考慮與模式不匹配的輸入。 這反過來會使與正則表達式模式近似匹配的不受約束輸入的性能顯著降低。
若要解決此問題,可執行下列操作:
開發模式時,應考慮回溯對正則表達式引擎的性能的影響程度,特別是當正則表達式設計用于處理不受約束的輸入時。 有關詳細信息,請參閱控制回溯部分。
使用無效輸入、接近有效的輸入以及有效輸入對正則表達式進行完全測試。 若要為特定正則表達式隨機生成輸入,可以使用 Rex,這是 Microsoft Research 提供的正則表達式探索工具。
.NET 正則表達式對象模型的核心是 xref:System.Text.RegularExpressions.Regex?displayProperty=nameWithType 類,表示正則表達式引擎。 通常,影響正則表達式性能的單個最大因素是 xref:System.Text.RegularExpressions.Regex 引擎的使用方式。 定義正則表達式需要將正則表達式引擎與正則表達式模式緊密耦合。 無論該耦合過程是需要通過向其構造函數傳遞正則表達式模式來實例化 xref:System.Text.RegularExpressions.Regex 還是通過向其傳遞正則表達式模式和要分析的字符串來調用靜態方法,都必然會消耗大量資源。
可將正則表達式引擎與特定正則表達式模式耦合,然后使用該引擎以若干種方式匹配文本:
可以調用靜態模式匹配方法,如 xref:System.Text.RegularExpressions.Regex.Match(System.String%2CSystem.String)?displayProperty=nameWithType。 這不需要實例化正則表達式對象。
可以實例化一個 xref:System.Text.RegularExpressions.Regex 對象并調用已解釋的正則表達式的實例模式匹配方法。 這是將正則表達式引擎綁定到正則表達式模式的默認方法。 如果實例化 xref:System.Text.RegularExpressions.Regex 對象時未使用包括 options
標記的 xref:System.Text.RegularExpressions.RegexOptions.Compiled 自變量,則會生成此方法。
可以實例化一個 xref:System.Text.RegularExpressions.Regex 對象并調用已編譯的正則表達式的實例模式匹配方法。 當使用包括 xref:System.Text.RegularExpressions.Regex 標記的 options
參數實例化 xref:System.Text.RegularExpressions.RegexOptions.Compiled 對象時,正則表達式對象表示已編譯的模式。
可以創建一個與特定正則表達式模式緊密耦合的特殊用途的 xref:System.Text.RegularExpressions.Regex 對象,編譯該對象,并將其保存到獨立程序集中。 為此,可調用 xref:System.Text.RegularExpressions.Regex.CompileToAssembly*?displayProperty=nameWithType 方法。
這種調用正則表達式匹配方法的特殊方式會對應用程序產生顯著影響。 以下各節討論何時使用靜態方法調用、已解釋的正則表達式和已編譯的正則表達式,以改進應用程序的性能。
建議將靜態正則表達式方法用作使用同一正則表達式重復實例化正則表達式對象的替代方法。 與正則表達式對象使用的正則表達式模式不同,靜態方法調用所使用的模式中的操作代碼或已編譯的 Microsoft 中間語言 (MSIL) 由正則表達式引擎緩存在內部。
例如,事件處理程序會頻繁調用其他方法來驗證用戶輸入。 下面的代碼中反映了這一點,其中一個 xref:System.Windows.Forms.Button 控件的 xref:System.Windows.Forms.Control.Click 事件用于調用名為 IsValidCurrency
的方法,該方法檢查用戶是否輸入了后跟至少一個十進制數的貨幣符號。
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
方法的一個非常低效的實現。 請注意,每個方法調用使用相同模式重新實例化 xref:System.Text.RegularExpressions.Regex 對象。 這反過來意味著,每次調用該方法時,都必須重新編譯正則表達式模式。
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); } }
應將此低效代碼替換為對靜態 xref:System.Text.RegularExpressions.Regex.IsMatch(System.String%2CSystem.String)?displayProperty=nameWithType 方法的調用。 這樣便不必在你每次要調用模式匹配方法時都實例化 xref:System.Text.RegularExpressions.Regex 對象,還允許正則表達式引擎從其緩存中檢索正則表達式的已編譯版本。
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); } }
默認情況下,將緩存最后 15 個最近使用的靜態正則表達式模式。 對于需要大量已緩存的靜態正則表達式的應用程序,可通過設置 Regex.CacheSize 屬性來調整緩存大小。
此示例中使用的正則表達式 \p{Sc}+\s*\d+
可驗證輸入字符串是否包含一個貨幣符號和至少一個十進制數。 模式的定義如下表所示。
模式 | 描述 |
---|---|
\p{Sc}+ | 與 Unicode 符號、貨幣類別中的一個或多個字符匹配。 |
\s* | 匹配零個或多個空白字符。 |
\d+ | 匹配一個或多個十進制數字。 |
將解釋未通過 RegexOptions.Compiled 選項的規范綁定到正則表達式引擎的正則表達式模式。 在實例化正則表達式對象時,正則表達式引擎會將正則表達式轉換為一組操作代碼。 調用實例方法時,操作代碼會轉換為 MSIL 并由 JIT 編譯器執行。 同樣,當調用一種靜態正則表達式方法并且在緩存中找不到該正則表達式時,正則表達式引擎會將該正則表達式轉換為一組操作代碼并將其存儲在緩存中。 然后,它將這些操作代碼轉換為 MSIL,以便于 JIT 編譯器執行。 已解釋的正則表達式會減少啟動時間,但會使執行速度變慢。 因此,在少數方法調用中使用正則表達式時或調用正則表達式方法的確切數量未知但預期很小時,使用已解釋的正則表達式的效果最佳。 隨著方法調用數量的增加,執行速度變慢對性能的影響會超過減少啟動時間帶來的性能改進。
將編譯通過 RegexOptions.Compiled 選項的規范綁定到正則表達式引擎的正則表達式模式。 這意味著,當實例化正則表達式對象時或當調用一種靜態正則表達式方法并且在緩存中找不到該正則表達式時,正則表達式引擎會將該正則表達式轉換為一組中間操作代碼,這些代碼之后會轉換為 MSIL。 調用方法時,JIT 編譯器將執行該 MSIL。 與已解釋的正則表達式相比,已編譯的正則表達式增加了啟動時間,但執行各種模式匹配方法的速度更快。 因此,相對于調用的正則表達式方法的數量,因編譯正則表達式而產生的性能產生了改進。
簡言之,當你使用特定正則表達式調用正則表達式方法相對不頻繁時,建議使用已解釋的正則表達式。 當你使用特定正則表達式調用正則表達式方法相對頻繁時,應使用已編譯的正則表達式。 很難確定已解釋的正則表達式執行速度減慢超出啟動時間減少帶來的性能增益的確切閾值,或已編譯的正則表達式啟動速度減慢超出執行速度加快帶來的性能增益的閾值。 這依賴于各種因素,包括正則表達式的復雜程度和它處理的特定數據。 若要確定已解釋或已編譯的正則表達式是否可為特定應用程序方案提供最佳性能,可以使用 Diagnostics.Stopwatch 類來比較其執行時間。
下面的示例比較了已編譯和已解釋正則表達式在讀取 Theodore Dreiser 所著《金融家》中前十句文本和所有句文本時的性能。 如示例輸出所示,當只對匹配方法的正則表達式進行十次調用時,已解釋的正則表達式與已編譯的正則表達式相比,可提供更好的性能。 但是,當進行大量調用(在此示例中,超過 13,000 次調用)時,已編譯的正則表達式可提供更好的性能。
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
該示例中使用的正則表達式模式 \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]
的定義如下表所示。
模式 | 描述 |
---|---|
\b | 在單詞邊界處開始匹配。 |
\w+ | 匹配一個或多個單詞字符。 |
(\r?\n)|,?\s) | 匹配零個或一個回車符后跟一個換行符,或零個或一個逗號后跟一個空白字符。 |
(\w+((\r?\n)|,?\s))* | 匹配一個或多個單詞字符的零個或多個事例,后跟零個或一個回車符和換行符,或后跟零個或一個逗號、一個空格字符。 |
\w+ | 匹配一個或多個單詞字符。 |
[.?:;!] | 匹配句號、問號、冒號、分號或感嘆號。 |
借助 .NET,還可以創建包含已編譯正則表達式的程序集。 這樣會將正則表達式編譯對性能造成的影響從運行時轉移到設計時。 但是,這還涉及一些其他工作:必須提前定義正則表達式并將其編譯為程序集。 然后,編譯器在編譯使用該程序集的正則表達式的源代碼時,可以引用此程序集。 程序集內的每個已編譯正則表達式都由從 xref:System.Text.RegularExpressions.Regex 派生的類來表示。
若要將正則表達式編譯為程序集,可調用 Regex.CompileToAssembly(RegexCompilationInfo[], AssemblyName) 方法并向其傳遞表示要編譯的正則表達式的 RegexCompilationInfo 對象數組和包含有關要創建的程序集的信息的 AssemblyName 對象。
建議你在以下情況下將正則表達式編譯為程序集:
如果你是要創建可重用正則表達式庫的組件開發人員。
如果你預期正則表達式的模式匹配方法要被調用的次數無法確定 -- 從任意位置,次數可能為一次兩次到上千上萬次。 與已編譯或已解釋的正則表達式不同,編譯為單獨程序集的正則表達式可提供與方法調用數量無關的一致性能。
如果使用已編譯的正則表達式來優化性能,則不應使用反射來創建程序集,加載正則表達式引擎并執行其模式匹配方法。 這要求你避免動態生成正則表達式模式,并且要在創建程序集時指定模式匹配選項(如不區分大小寫的模式匹配)。 它還要求將創建程序集的代碼與使用正則表達式的代碼分離。
下面的示例演示如何創建包含已編譯的正則表達式的程序集。 它創建包含一個正則表達式類 SentencePattern
的程序集 RegexLib.dll
,其中包含已解釋與已編譯的正則表達式部分中使用的句子匹配的正則表達式模式。
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); } }
在將示例編譯為可執行文件并運行時,它會創建一個名為 RegexLib.dll
的程序集。 正則表達式用名為 Utilities.RegularExpressions.SentencePattern
并由 RegularExpressions.Regex 派生的類來表示。 然后,下面的示例使用已編譯正則表達式,從 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.
通常,正則表達式引擎使用線性進度在輸入字符串中移動并將其編譯為正則表達式模式。 但是,當在正則表達式模式中使用不確定限定符(如 *
、+
和 ?
)時,正則表達式引擎可能會放棄一部分成功的分部匹配,并返回以前保存的狀態,以便為整個模式搜索成功匹配。 此過程稱為回溯。
支持回溯可為正則表達式提供強大的功能和靈活性。 還可將控制正則表達式引擎操作的職責交給正則表達式開發人員來處理。 由于開發人員通常不了解此職責,因此其誤用回溯或依賴過多回溯通常會顯著降低正則表達式的性能。 在最糟糕的情況下,輸入字符串中每增加一個字符,執行時間會加倍。 實際上,如果過多使用回溯,則在輸入與正則表達式模式近似匹配時很容易創建無限循環的編程等效形式;正則表達式引擎可能需要幾小時甚至幾天來處理相對短的輸入字符串。
通常,盡管回溯不是匹配所必需的,但應用程序會因使用回溯而對性能產生負面影響。 例如,正則表達式 \b\p{Lu}\w*\b
將匹配以大寫字符開頭的所有單詞,如下表所示。
模式 | 描述 |
---|---|
\b | 在單詞邊界處開始匹配。 |
\p{Lu} | 匹配大寫字符。 |
\w* | 匹配零個或多個單詞字符。 |
\b | 在單詞邊界處結束匹配。 |
由于單詞邊界與單詞字符不同也不是其子集,因此正則表達式引擎在匹配單詞字符時無法跨越單詞邊界。 這意味著,對于此正則表達式而言,回溯對任何匹配的總體成功不會有任何貢獻 -- 由于正則表達式引擎被強制為單詞字符的每個成功的初步匹配保存其狀態,因此它只會降低性能。
如果確定不需要回溯,可使用 (?>subexpression)
語言元素(被稱為原子組)來禁用它。 下面的示例通過使用兩個正則表達式來分析輸入字符串。 第一個正則表達式 \b\p{Lu}\w*\b
依賴于回溯。 第二個正則表達式 \b\p{Lu}(?>\w*)\b
禁用回溯。 如示例輸出所示,這兩個正則表達式產生的結果相同。
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
在許多情況下,在將正則表達式模式與輸入文本匹配時,回溯很重要。 但是,過度回溯會嚴重降低性能,并且會產生應用程序已停止響應的感覺。 特別需要指出的是,當嵌套限定符并且與外部子表達式匹配的文本為與內部子表達式匹配的文本的子集時,尤其會出現這種情況。
例如,正則表達式模式 ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$
用于匹配至少包括一個字母數字字符的部件號。 任何附加字符可以包含字母數字字符、連字符、下劃線或句號,但最后一個字符必須為字母數字。 美元符號用于終止部件號。 在某些情況下,由于限定符嵌套并且子表達式 [0-9A-Z]
是子表達式 [-.\w]*
的子集,因此此正則表達式模式會表現出極差的性能。
在這些情況下,可通過移除嵌套限定符并將外部子表達式替換為零寬度預測先行和回顧斷言來優化正則表達式性能。 預測先行和回顧斷言是定位點;它們不在輸入字符串中移動指針,而是通過預測先行或回顧來檢查是否滿足指定條件。 例如,可將部件號正則表達式重寫為 ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$
。 此正則表達式模式的定義如下表所示。
模式 | 描述 |
---|---|
^ | 從輸入字符串的開頭部分開始匹配。 |
[0-9A-Z] | 匹配字母數字字符。 部件號至少要包含此字符。 |
[-.\w]* | 匹配零個或多個任意單詞字符、連字符或句號。 |
\$ | 匹配美元符號。 |
(?<=[0-9A-Z]) | 查看作為結束的美元符號,以確保前一個字符是字母數字。 |
$ | 在輸入字符串末尾結束匹配。 |
下面的示例演示了如何使用此正則表達式來匹配包含可能部件號的數組。
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 中的正則表達式語言包括以下可用于消除嵌套限定符的語言元素。 有關詳細信息,請參閱分組構造。
語言元素 | 描述 |
---|---|
(?= subexpression ) | 零寬度正預測先行。 預測先行當前位置,以確定 subexpression 是否與輸入字符串匹配。 |
(?! subexpression ) | 零寬度負預測先行。 預測先行當前位置,以確定 subexpression 是否不與輸入字符串匹配。 |
(?<= subexpression ) | 零寬度正回顧。 回顧后發當前位置,以確定 subexpression 是否與輸入字符串匹配。 |
(?<! subexpression ) | 零寬度負回顧。 回顧后發當前位置,以確定 subexpression 是否不與輸入字符串匹配。 |
如果正則表達式處理與正則表達式模式大致匹配的輸入,則通常依賴于會嚴重影響其性能的過度回溯。 除認真考慮對回溯的使用以及針對大致匹配輸入對正則表達式進行測試之外,還應始終設置一個超時值以確保最大程度地降低過度回溯的影響(如果有)。
正則表達式超時間隔定義了在超時前正則表達式引擎用于查找單個匹配項的時間長度。默認超時間隔為 Regex.InfiniteMatchTimeout,這意味著正則表達式不會超時。可以按如下所示重寫此值并定義超時間隔:
在實例化一個 Regex 對象(通過調用 Regex(String, RegexOptions, TimeSpan) 構造函數)時,提供一個超時值。
調用靜態模式匹配方法,如 Regex.Match(String, String, RegexOptions, TimeSpan) 或 Regex.Replace(String, String, String, RegexOptions, TimeSpan),其中包含 matchTimeout 參數。
對于通過調用 Regex.CompileToAssembly 方法創建的已編譯的正則表達式,可調用帶有 TimeSpan 類型的參數的構造函數。
如果定義了超時間隔并且在此間隔結束時未找到匹配項,則正則表達式方法將引發 RegexMatchTimeoutException 異常。 在異常處理程序中,可以選擇使用一個更長的超時間隔來重試匹配、放棄匹配嘗試并假定沒有匹配項,或者放棄匹配嘗試并記錄異常信息以供未來分析。
下面的示例定義了一種 GetWordData 方法,此方法實例化了一個正則表達式,使其具有 350 毫秒的超時間隔,用于計算文本文件中的詞語數和一個詞語中的平均字符數。 如果匹配操作超時,則超時間隔將延長 350 毫秒并重新實例化 Regex 對象。 如果新的超時間隔超過 1 秒,則此方法將再次向調用方引發異常。
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 中的正則表達式支持許多分組構造,這樣,便可以將正則表達式模式分組為一個或多個子表達式。 .NET 正則表達式語言中最常用的分組構造為 (subexpression)(用于定義編號捕獲組)和 (?subexpression)(用于定義命名捕獲組)。 分組構造是創建反向引用和定義要應用限定符的子表達式時所必需的。
但是,使用這些語言元素會產生一定的開銷。 它們會導致用最近的未命名或已命名捕獲來填充 GroupCollection 屬性返回的 Match.Groups 對象,如果單個分組構造已捕獲輸入字符串中的多個子字符串,則還會填充包含多個 CaptureCollection 對象的特定捕獲組的 Group.Captures 屬性返回的 Capture 對象。
通常,只在正則表達式中使用分組構造,這樣可對其應用限定符,而且以后不會使用這些子表達式捕獲的組。 例如,正則表達式 \b(\w+[;,]?\s?)+[.?!] 用于捕獲整個句子。 下表描述了此正則表達式模式中的語言元素及其對 Match 對象的 Match.Groups 和 Group.Captures 集合的影響。
模式 | 描述 |
---|---|
\b | 在單詞邊界處開始匹配。 |
\w+ | 匹配一個或多個單詞字符。 |
[;,]? | 匹配零個或一個逗號或分號。 |
\s? | 匹配零個或一個空白字符。 |
(\w+[;,]?\s?)+ | 匹配以下一個或多個事例:一個或多個單詞字符,后跟一個可選逗號或分號,一個可選的空白字符。 用于定義第一個捕獲組,它是必需的,以便將重復多個單詞字符的組合(即單詞)后跟可選標點符號,直至正則表達式引擎到達句子末尾。 |
[.?!] | 匹配句號、問號或感嘆號。 |
如下面的示例所示,當找到匹配時,GroupCollection 和 CaptureCollection 對象都將用匹配中的捕獲內容來填充。 在此情況下,存在捕獲組 (\w+[;,]?\s?),因此可對其應用 + 限定符,從而使得正則表達式模式可與句子中的每個單詞匹配。 否則,它將匹配句子中的最后一個單詞。
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.
當你只使用子表達式來對其應用限定符并且你對捕獲的文本不感興趣時,應禁用組捕獲。 例如,(?:subexpression) 語言元素可防止應用此元素的組捕獲匹配的子字符串。 在下面的示例中,上一示例中的正則表達式模式更改為 \b(?:\w+[;,]?\s?)+[.?!]。 正如輸出所示,它禁止正則表達式引擎填充 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) 語言元素。 此元素可防止在它應用的組中捕獲匹配的子字符串。 它不在任何嵌套的組中禁用子字符串捕獲。
使用 ExplicitCapture 選項。 在正則表達式模式中禁用所有未命名或隱式捕獲。 使用此選項時,只能捕獲與使用 (?subexpression) 語言元素定義的命名組匹配的子字符串。 可將 ExplicitCapture 標記傳遞給 options 類構造函數的 Regex 參數或 options 靜態匹配方法的 Regex 參數。
在 n 語言元素中使用 (?imnsx) 選項。 此選項將在元素出現的正則表達式模式中的點處禁用所有未命名或隱式捕獲。 捕獲將一直禁用到模式結束或 (-n) 選項啟用未命名或隱式捕獲。 有關詳細信息,請參閱 其他構造。
在 n 語言元素中使用 (?imnsx:subexpression) 選項。 此選項可在 subexpression 中禁用所有未命名或隱式捕獲。 同時禁用任何未命名或隱式的嵌套捕獲組進行的任何捕獲。
讀到這里,這篇“.NET正則表達式最佳的使用方法是什么”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。