代码改变世界

【原】检测是否包含特定字符串的几种方法以及性能比较

2011-03-02 16:21  拖鞋不脱  阅读(5281)  评论(12编辑  收藏  举报

对于一串字符串"abfeuiowqjiqopeuwqiopewq",检测其中是否包含特定字符串"oweu",我常用的方法如下:

if (row.IndexOf(pattern) > -1)

以前从来没有想过这样有什么问题,但最近需要处理较大数据量的字符串,需要从各个方面考虑如何提高处理的效率,其中“检测是否包含特定字符串”也是重要的一环。

几种方法

在.Net 2.0之后,其实有更简洁的方法 row.Contains(pattern),直接返回布尔值。看起来这种方法更原生态一些,可能效率更高。

此外,从正则表达式的角度来想,检测是否包含特定字符串其实是正则表达式最简单的一种应用,所以利用 Regex.IsMatch(row,pattern) 也是一种选择。

事实上,继续细分,会有如下几种方式:

1、Contains 方法

直接调用 row.Contains(pattern); 那么 Contains 方法到底是怎么实现的呢?这里有童鞋告诉我们 http://stackoverflow.com/questions/498686/net-is-string-contains-faster-than-string-indexof ,Contains 实际是调用了 IndexOf 方法。

public bool Contains(string value)
{
    return (this.IndexOf(value, StringComparison.Ordinal) >= 0);
}
注意,这里的 IndexOf 方法还包含了第二个参数 StringComparison.Ordinal,而不带参数的 IndexOf 方法,其实应用的是默认的 StringComparison.CurrentCulture 参数。
至于这两种参数在性能上有什么区别,我们可以猜测默认的参数由于和系统的 Culture 相关,所以可能会有额外的性能开销。

2、IndexOf 方法

如在 Contains方法里讨论的,即使是同样调用 IndexOf 方法,也可能由于 StringComparison 参数的不同而有不同的效率,所以实际上 IndexOf 方法可以再细分为

row.IndexOf(pattern) > –1;

row.IndexOf(pattern,StringComparison.Ordinal) > –1;

3、Regex

使用正则表达式,也可以分为两种情况:

1) 直接调用 Regex 的静态方法。Regex.IsMatch(row,pattern);

2) 构造一个 Regex 类型的实例,然后用实例的方法来检测。这样的好处是在初始化的时候就设好正则规则,而不需要每次动态设置。

而构建一个 Regex 类型的实例也会有多种方法,最简单的当然是直接 var regex = new Regex(pattern); 也可以在构建时增加 RegexOptions.Compiled 参数,将正则规则编译到内存中,还可以更麻烦一点,利用 Regex.CompileToAssembly 方法,将正则规则编译到 Dll 里,然后引用 Dll,这样节省了每次编译的消耗。具体如何使用 Regex.CompileToAssembly 方法,请参考 http://topic.csdn.net/u/20100510/10/c140542a-5624-4c5c-8e52-587b50315d05.html

效率比较

在做测试之前,我们可以先根据上述几种方法的特点做个预测:

  1. IndexOf 方法中,带 StringComparison.Ordinal 参数的应该比不带的效率高,而 Contains 方法可能效率稍弱于带 StringComparison.Ordinal 参数的 IndexOf 方法,因为毕竟多了一层方法的调用,同时效率应该高于不带参数的 IndexOf 方法。
  2. Regex 方法中,构造实例应该比静态方法的效率高,而使用 CompileToAssembly 的效率应该高于使用 RegexOptions.Compiled 参数,而使用 RegexOptions.Compiled 参数的效率应该高于不使用参数。
  3. Regex 的方式和 IndexOf 的方式本质不同,无法单凭猜测作出比较。

下面就做一个性能测试。测试数据是一个1G的文件,大概有一百万行的数据,每行进行一次比较。每种方法运行十次,取后九次的平均运行时间(避免第一次有 .Net 冷启的差异)。共测试了8种方法:

  1. row.Contains(pattern)
  2. row.IndexOf(pattern) >= 0
  3. row.IndexOf(pattern) > –1
  4. row.IndexOf(pattern, StringComparison.Ordinal) >= 0
  5. var regex = new Regex(pattern, RegexOptions.Compiled);
    regex.IsMatch(row)
  6. var regex = new Regex(pattern);
    regex.IsMatch(row)
  7. Regex.IsMatch(row, pattern)
  8. var regex = new CompiledRegex.GVD(); //编译到Dll里面的正则表达式
    regex.IsMatch(row)

测试结果如下:

image

可以看到,这里 Contains 方法和带 StringComparison.Ordinal 参数的方法以及非静态的 Regex方法效率类似,Regex 方法似乎性能略优,但很不明显;静态的 Regex 方法属于第二梯队,而直接调用无参的 IndexOf 方法效率垫底。基本符合我们之前的猜测。

结论

  1. 一般情况下直接使用 Contains 方法,因为该方法最简洁明了,而且效率也很高。
  2. 如果对性能比较偏执,而要处理的数据量并不是特别大的情况,使用 row.IndexOf(pattern, StringComparison.Ordinal) >= 0 方法。不用 Regex 的原因是,在构造 Regex 实例的时候也要消耗一定性能,如果要处理的数据又不是特别大,使用 Regex 带来的好处无法抵消这一消耗,总的性能是降低的。
  3. 在 Regex 的使用中,编译到 Dll 中性能最好,但也最麻烦,除非非常复杂的正则,否则没必要;使用 RegexOptions.Compiled 编译到内存中在每次处理时性能会提升,但最初编译的性能消耗也很可观,所以要视需要处理的数据量而定。Regex 的静态方法虽然方便,但只推荐在不需要重复使用正则时使用。
  4. 在这里看起来使用 > –1 和 >= 0,虽然结果相同,但性能似乎有所不同,不知道 >= 0是否因为包含了一个“或”的原因,导致性能略有下降,这点存疑。

此外,这里还有一篇研究 IndexOf 的性能的文章 http://www.dotnetperls.com/indexof。其中比较有意思的一点是,对于单独一个字母 a,使用 IndexOf('a') 的性能比 IndexOf("a") 的性能高很多