高效搜索不区分大小写的ascii子串

时间:2011-10-20 15:53:28

标签: c#

我实际上正在优化大字符串的正则表达式转换。正如一些对Regex.Replace的调用花费了大量时间我插入了条件调用 - 这就行了

if (str.IndexOf("abc", 0, StringComparison.CurrentCultureIgnoreCase) > 0)
    str1 = Regex.Replace(str, "abc...", string.Empty, RegexOptions.IgnoreCase);

令我惊讶的是,结果并不令人信服。有时更好,有时不是。甚至更糟。所以我测量了不区分大小写的IndexOf(我也尝试过StringComparison.OrdinalIgnoreCase)的性能,发现它可能比Regex.Match慢,即这个测试:

if( Regex.Match(str,"abc", RegexOptions.IgnoreCase).Success )...

特别是对于不匹配的大字符串(ascii)字符串“abc”,我发现Regex.Match的速度提高了4倍。

即使这个程序比IndexOf更快:

string s1 = str.ToLower();
if( s1.IndexOf("abc") ) ...

有谁知道更好的解决方案?

2 个答案:

答案 0 :(得分:0)

因为indexOf是O(n * m),其中RegEx将被设为O(n + m)(其中n =字符串长度,m =搜索字符串长度)。

如果您正在严格搜索子字符串,那么阅读字符串搜索算法至少要了解预期的速度(以http://en.wikipedia.org/wiki/Substring_search开头)会很有用。

注意:文化敏感性比较可能比Ordinal明显慢(取决于您的场景,您可能无法使用Ordinal版本)。

与任何表演问题一样,需要离开和衡量。在带有Regex.isMatch的indexOf中,明确的赢家是Regex。我之所料,indexOf的行为不应该执行搜索字符串的任何预编译,必须使用O(n + m)算法,而Regex必须使用更优化的实现。

尝试测量以下搜索 - 我得到差不多5倍的差异,支持Regex进行100K操作。

   static void Main(string[] args)
    {
        var stringToSearch = "AAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAAbb";
        Regex regExp = new Regex(stringToSearch, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

        var sourceText = "AAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAAbb";

       const int Iterations = 100000;

       Stopwatch watch = new Stopwatch();
       watch.Start();
       for (int i = 0; i < Iterations; i++)
       {
           regExp.IsMatch(sourceText);
       }
       watch.Stop();
       Console.WriteLine("RegExp: {0} ms", watch.ElapsedMilliseconds);

       watch = new Stopwatch();
       watch.Start();
       for (int i = 0; i < Iterations; i++)
       {
           sourceText.IndexOf(stringToSearch, StringComparison.OrdinalIgnoreCase);
       }
       watch.Stop();
       Console.WriteLine("RegExp: {0} ms", watch.ElapsedMilliseconds);
       Console.ReadLine();
    }

答案 1 :(得分:0)

最后,我为匹配测试编写了自己的函数。这是:

private static bool HasAsciiSubstring(string str, string sub)
{
    char[] ss = sub.ToCharArray();

    // Similar stupid small optimizations bring 30% speeding-up.
    int ss_len = ss.Length;
    for (int j = 0; j < ss_len; ++j)
        ss[j] = (char)((byte)ss[j] | 32);

    byte ss0 = (byte)ss[0];
    int len = str.Length - ss_len;
    for (int i = 0; i < len; ++i)
    {
        if ((str[i] | 32) == ss0)
        {
            bool bOk = true;
            for (int j = 1; j < ss_len; ++j)
            {
                if ((str[i + j] | 32) != ss[j])
                {
                    bOk = false;
                    break;
                }
            }
            if (bOk)
                return true;
        }
    }

    return false;
}

此方法在WP7平台上提供了最佳结果(针对选定的大字符串)。这是

  • 比Regex.Match快2倍(str,sub,RegexOptions.IgnoreCase)
  • 比str.IndexOf快10倍(sub,0,StringComparison.CurrentCultureIgnoreCase)
  • str.IndexOf(sub,0,StringComparison.OrdinalIgnoreCase)比CurrentCultureIgnoreCase慢了30%。

我需要一个能够在WP7,MonoDroid / Touch和桌面上表现令人满意的代码。因此我编写了一个小型WPF应用程序来测试至少桌面:

  • Regex.Match()比HasAsciiSubstring()快2倍。
  • OrdinalIgnoreCase比CurrentCultureIgnoreCase慢2.5倍
  • 其余类似

我相对较新的C#/ .Net(仅2年经验),但在其他平台上编程了数十年(主要是C / C ++)。我必须说明这对我来说是一种令人震惊的经历:

  • 效率低下的C#字符串函数
  • 效率低下的C#作为语言工具。 (我很确定用C ++编写的相同代码会表现得更好。)
  • 这种复杂的文化业务以及CurrentCultureIgnoreCase比OrdinalIgnoreCase表现更好的事实。 (看起来每个经验丰富的C#程序员 - 包括我周围的人 - 声称恰恰相反。)