【问题标题】:Regex.IsMatch vs string.ContainsRegex.IsMatch 与 string.Contains
【发布时间】:2010-06-03 00:51:19
【问题描述】:

这两个等效表达式在速度/内存使用方面是否有任何差异:

Regex.IsMatch(Message, "1000")

对比

Message.Contains("1000")

任何一种情况比其他更好?

本题的上下文如下: 我正在对包含 Regex 表达式的遗留代码进行一些更改,以查找字符串是否包含在另一个字符串中。作为遗留代码,我没有对其进行任何更改,并且在代码审查中有人建议 Regex.IsMatch 应该由 string.Contains 替换。所以我想知道这种改变是否值得。

【问题讨论】:

标签: .net regex string


【解决方案1】:

对于简单的情况String.Contains 会给你更好的性能,但String.Contains 不允许你做复杂的模式匹配。将String.Contains 用于非模式匹配场景(如您的示例中的场景),并将正则表达式用于需要进行更复杂模式匹配的场景。

正则表达式有一定的开销(表达式解析、编译、执行等),而像 String.Contains 这样的简单方法根本没有,这就是为什么 String.Contains 在像你这样的例子。

【讨论】:

  • 显然这是一个非常古老的答案,但也许你可以提供你的来源? @user279470 似乎提供了一些很好的证据证明这个答案是错误的。
  • 然后是与 Andrew stackoverflow.com/a/17579471/289992 一致的基准测试,任你选!
【解决方案2】:

String.Containscompiled regular expression 相比会更慢。甚至相当慢!

您可以运行此基准测试它:

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

【讨论】:

  • 在 60kb 的随机 EDIFACT INVRP 文件上运行它,中途填充了“文件中存在”: containsTimer: 84925 regexTimer: 10633
  • 虽然它不是 String.Contains(),但我只是在我的程序中修改了一个搜索和替换函数以使用编译的 Regex 对象而不是 'Value.ToString.IndexOf(SearchString, StringComparison.CurrentCultureIgnoreCase)' .我使用 >44,000 行 DataGridView(1,921 次替换)的全部替换测试从 ~7.5 分钟缩短到 ~30 秒。
  • 如果你把 .IndexOf 添加到这个,它甚至比 .Contains 还要慢。正则表达式真是太棒了。
  • content.Contains win。因为new Regex("this exists in file") != content.Contains(searchString)... 但是new Regex(".+this exists in file.+")
  • 是的,当Contains 找到更接近字符串Contains 开头的搜索字符串时获胜。否则正则表达式获胜,我想我们应该说正则表达式总体上具有更好的性能,但Contains 胜过小字符串(假设低于 1000-2000chars)
【解决方案3】:

要确定哪个速度最快,您必须对自己的系统进行基准测试。但是,正则表达式很复杂,String.Contains() 可能是最快的,在您的情况下也是最简单的解决方案。

String.Contains() 的实现最终将调用本机方法IndexOfString(),而该实现只有微软知道。但是,实现此方法的一个很好的算法是使用所谓的Knuth–Morris–Pratt algorithm。该算法的复杂度为 O(m + n) 其中 m 是您要搜索的字符串的长度,而 n 是长度您正在搜索的字符串,使其成为一种非常有效的算法。

实际上,使用正则表达式搜索的效率可能会低O(n),具体取决于实现,因此在某些情况下它可能仍然具有竞争力。只有基准才能确定这一点。

如果您真的关心搜索速度,Christian Charras 和 Thierry Lecroq 有很多关于鲁昂大学 exact string matching algorithms 的资料。

【讨论】:

    【解决方案4】:

    @user279470 我正在寻找一种有效的方法来计算单词只是为了好玩,结果遇到了this。我给了它 OpenOffice Thesaurus dat 文件进行迭代。总字数达到1575423。

    现在,我的最终目标并没有使用 contains,但有趣的是看到调用正则表达式的不同方式可以使其更快。我创建了一些其他方法来比较正则表达式的实例使用和与 RegexOptions.compiled 的静态使用。

    public static class WordCount
    {
        /// <summary>
        /// Count words with instaniated Regex.
        /// </summary>
        public static int CountWords4(string s)
        {
            Regex r = new Regex(@"[\S]+");
            MatchCollection collection = r.Matches(s);
            return collection.Count;
        }
        /// <summary>
        /// Count words with static compiled Regex.
        /// </summary>
        public static int CountWords1(string s)
        {
            MatchCollection collection = Regex.Matches(s, @"[\S]+", RegexOptions.Compiled);
            return collection.Count;
        }
        /// <summary>
        /// Count words with static Regex.
        /// </summary>
        public static int CountWords3(string s)
        {
            MatchCollection collection = Regex.Matches(s, @"[\S]+");
            return collection.Count;
        }
    
        /// <summary>
        /// Count word with loop and character tests.
        /// </summary>
        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
    • 包含Timer.ElapsedMilliseconds 1074

    【讨论】:

      【解决方案5】:

      我自己的基准测试结果似乎与 user279470 的基准测试结果相矛盾。

      在我的用例中,我想用一些 OR 运算符检查一个简单的正则表达式是否有 4 个值,而不是使用 4 x String.Contains()

      即使是 4 x String.Contains(),我发现 String.Contains() 也快 5 倍。

      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<string>();
      
                  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<string> { "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());
              }
          }
      }
      

      【讨论】:

      • 对于小字符串包含胜利,但是当我用simulatedTestString += RandomString(500); 替换随机字符串添加行时,结果几乎相等,对于较大的字符串,我相信正则表达式会获胜
      【解决方案6】:

      是的,对于这个任务,string.Contains 几乎肯定会更快并且使用更少的内存。当然,这里没有理由使用正则表达式。

      【讨论】:

        【解决方案7】:

        正则表达式匹配由 .NET Framework 的正则表达式引擎处理。下面是来自msdn article 的部分,它准确地指定了何时使用正则表达式与字符串搜索和替换函数:

        String 类包括许多字符串搜索和替换方法,当您想要在较大的字符串中定位文字字符串时可以使用这些方法。 正则表达式在您想要定位较大字符串中的多个子字符串之一时,或者当您想要识别字符串中的模式时最有用,如下例所示。

        文章中有两个例子说明了这一点。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-06-26
          • 2013-10-22
          • 2010-09-29
          • 1970-01-01
          • 2010-12-10
          • 2013-01-04
          • 2020-12-06
          相关资源
          最近更新 更多