Regex.IsMatch vs string.Contains

C’è qualche differenza nell’uso della velocità / memoria per queste due espressioni equivalenti:

Regex.IsMatch(Message, "1000") 

vs

 Message.Contains("1000") 

Qualche situazione in cui uno è migliore dell’altro?

Il contesto di questa domanda è il seguente: stavo apportando alcune modifiche al codice legacy che conteneva l’espressione Regex per scoprire se una stringa è contenuta all’interno di un’altra stringa. Essendo codice legacy non ho apportato alcuna modifica a questo e nella revisione del codice qualcuno ha suggerito che Regex.IsMatch debba essere sostituito da string.Contains. Quindi mi chiedevo se valesse la pena di apportare un cambiamento.

Per casi semplici, String.Contains offre prestazioni migliori, ma String.Contains non consente di eseguire la corrispondenza di modelli complessi. Utilizzare String.Contains per scenari di corrispondenza senza pattern (come quello nell’esempio) e utilizzare espressioni regolari per scenari in cui è necessario eseguire una corrispondenza di pattern più complessa.

Un’espressione regolare ha una certa quantità di overhead ad essa associata (analisi delle espressioni, compilazione, esecuzione, ecc.) Che un semplice metodo come String.Contains semplicemente non ha, il che è il motivo per cui String.Contains sovraperformsrà un’espressione regolare in esempi come il tuo .

String.Contains è più lento quando lo si confronta con un’espressione regolare compilata . Molto più lento, persino!

Puoi testarlo eseguendo questo benchmark:

 class Program { public static int FoundString; public static int FoundRegex; static void DoLoop(bool show) { const string path = "C:\\file.txt"; const int iterations = 1000000; var content = File.ReadAllText(path); const string searchString = "this exists in file"; var searchRegex = new Regex("this exists in file"); var containsTimer = Stopwatch.StartNew(); for (var i = 0; i < iterations; i++) { if (content.Contains(searchString)) { FoundString++; } } containsTimer.Stop(); var regexTimer = Stopwatch.StartNew(); for (var i = 0; i < iterations; i++) { if (searchRegex.IsMatch(content)) { FoundRegex++; } } regexTimer.Stop(); if (!show) return; Console.WriteLine("FoundString: {0}", FoundString); Console.WriteLine("FoundRegex: {0}", FoundRegex); Console.WriteLine("containsTimer: {0}", containsTimer.ElapsedMilliseconds); Console.WriteLine("regexTimer: {0}", regexTimer.ElapsedMilliseconds); Console.ReadLine(); } static void Main(string[] args) { DoLoop(false); DoLoop(true); return; } } 

Per determinare quale sia il più veloce, dovrai confrontare il tuo sistema. Tuttavia, le espressioni regolari sono complesse e ci sono String.Contains() probabilità che String.Contains() sia il più veloce e nel tuo caso anche la soluzione più semplice.

L’implementazione di String.Contains() chiamerà infine il metodo nativo IndexOfString() e l’implementazione di ciò è nota solo a Microsoft. Tuttavia, un buon algoritmo per l’implementazione di questo metodo utilizza l’ algoritmo Knuth-Morris-Pratt . La complessità di questo algoritmo è O (m + n) dove m è la lunghezza della stringa che stai cercando e n è la lunghezza della stringa che stai cercando rendendolo un algoritmo molto efficiente.

In realtà, l’efficienza della ricerca usando l’espressione regolare può essere O (n) bassa a seconda dell’implementazione, quindi potrebbe essere ancora competitiva in alcune situazioni. Solo un punto di riferimento sarà in grado di determinare questo.

Se sei davvero preoccupato della velocità di ricerca, Christian Charras e Thierry Lecroq hanno molto materiale sugli algoritmi di corrispondenza esatta delle stringhe all’Université de Rouen.

@ user279470 Stavo cercando un modo efficace per contare le parole solo per divertimento e mi sono imbattuto in questo . Gli ho dato il file dat Thesaurus di OpenOffice per scorrere. Il conteggio totale delle parole è arrivato a 1575423.

Ora, il mio objective finale non aveva un uso per contiene, ma ciò che è stato interessante è stato vedere i diversi modi in cui è ansible chiamare regex che lo rendono ancora più veloce. Ho creato alcuni altri metodi per confrontare un uso di istanza di regex e un uso statico con RegexOptions.compiled.

 public static class WordCount { ///  /// Count words with instaniated Regex. ///  public static int CountWords4(string s) { Regex r = new Regex(@"[\S]+"); MatchCollection collection = r.Matches(s); return collection.Count; } ///  /// Count words with static compiled Regex. ///  public static int CountWords1(string s) { MatchCollection collection = Regex.Matches(s, @"[\S]+", RegexOptions.Compiled); return collection.Count; } ///  /// Count words with static Regex. ///  public static int CountWords3(string s) { MatchCollection collection = Regex.Matches(s, @"[\S]+"); return collection.Count; } ///  /// Count word with loop and character tests. ///  public static int CountWords2(string s) { int c = 0; for (int i = 1; i < s.Length; i++) { if (char.IsWhiteSpace(s[i - 1]) == true) { if (char.IsLetterOrDigit(s[i]) == true || char.IsPunctuation(s[i])) { c++; } } } if (s.Length > 2) { c++; } return c; } } 
  • regExCompileTimer.ElapsedMilliseconds 11787
  • regExStaticTimer.ElapsedMilliseconds 12300
  • regExInstanceTimer.ElapsedMilliseconds 13925
  • ContainsTimer.ElapsedMilliseconds 1074

I miei benchmark sembrano contraddire i risultati del benchmark di user279470.

Nel mio caso d’uso volevo controllare un semplice Regex con alcuni operatori OR per 4 valori rispetto a fare 4 x String.Contains() .

Anche con 4 x String.Contains() , ho trovato che String.Contains() era 5 volte più veloce.

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Text.RegularExpressions; namespace App.Tests.Performance { [TestClass] public class PerformanceTesting { private static Random random = new Random(); [TestMethod] public void RegexVsMultipleContains() { var matchRegex = new Regex("INFO|WARN|ERROR|FATAL"); var testStrings = new List(); int iterator = 1000000 / 4; // div 4 for each of log levels checked for (int i = 0; i < iterator; i++) { for (int j = 0; j < 4; j++) { var simulatedTestString = RandomString(50); if (j == 0) { simulatedTestString += "INFO"; } else if (j == 1) { simulatedTestString += "WARN"; } else if (j == 2) { simulatedTestString += "ERROR"; } else if (j == 3) { simulatedTestString += "FATAL"; } simulatedTestString += RandomString(50); testStrings.Add(simulatedTestString); } } int cnt; Stopwatch sw; ////////////////////////////////////////// // Multiple contains test ////////////////////////////////////////// cnt = 0; sw = new Stopwatch(); sw.Start(); for (int i = 0; i < testStrings.Count; i++) { bool isMatch = testStrings[i].Contains("INFO") || testStrings[i].Contains("WARN") || testStrings[i].Contains("ERROR") || testStrings[i].Contains("FATAL"); if (isMatch) { cnt += 1; } } sw.Stop(); Console.WriteLine("MULTIPLE CONTAINS: " + cnt + " " + sw.ElapsedMilliseconds); ////////////////////////////////////////// // Multiple contains using list test ////////////////////////////////////////// cnt = 0; sw = new Stopwatch(); sw.Start(); var searchStringList = new List { "INFO", "WARN", "ERROR", "FATAL" }; for (int i = 0; i < testStrings.Count; i++) { bool isMatch = searchStringList.Any(x => testStrings[i].Contains(x)); if (isMatch) { cnt += 1; } } sw.Stop(); Console.WriteLine("MULTIPLE CONTAINS USING LIST: " + cnt + " " + sw.ElapsedMilliseconds); ////////////////////////////////////////// // Regex test ////////////////////////////////////////// cnt = 0; sw = new Stopwatch(); sw.Start(); for (int i = 0; i < testStrings.Count; i++) { bool isMatch = matchRegex.IsMatch(testStrings[i]); if (isMatch) { cnt += 1; } } sw.Stop(); Console.WriteLine("REGEX: " + cnt + " " + sw.ElapsedMilliseconds); } public static string RandomString(int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); } } } 

Sì, per questa attività, string.Contain sarà quasi certamente più veloce e utilizzerà meno memoria. E ovviamente non c’è motivo di usare regex qui.