您好,登錄后才能下訂單哦!
本文小編為大家詳細(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)最佳性能可以采納的一些最佳做法。
通常,正則表達(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)大量時間(在有些情況下,需要許多個小時或許多天)來處理看似無害的文本。
例如,考慮一種很常用但很有問題的用于驗(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á)式探索工具。
.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)方法,都必然會消耗大量資源。
可將正則表達(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)用程序的性能。
建議將靜態(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ù)字。 |
將解釋未通過 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+ | 匹配一個或多個單詞字符。 |
[.?:;!] | 匹配句號、問號、冒號、分號或感嘆號。 |
借助 .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),以便為整個模式搜索成功匹配。 此過程稱為回溯。
支持回溯可為正則表達(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)這種情況。
例如,正則表達(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è)資訊頻道。
免責(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)容。