使用静态Regex.IsMatch与创建Regex的实例

时间:2009-01-05 20:06:30

标签: c# regex optimization

在C#中你应该有如下代码:

public static string importantRegex = "magic!";

public void F1(){
  //code
  if(Regex.IsMatch(importantRegex)){
    //codez in here.
  }
  //more code
}
public void main(){
  F1();
/*
  some stuff happens......
*/
  F1();
}

或者你应该坚持包含重要模式的正则表达式的实例吗?使用Regex.IsMatch的成本是多少?我想在每个正则表达式中都会创建一个NFA。根据我的理解,这个NFA的创作并非无足轻重。

7 个答案:

答案 0 :(得分:48)

与我典型的自我主义有很大的不同,我对这个答案有点逆转。

我在下面保留的原始答案是基于对.NET框架的 1.1 版本的检查。这是非常可耻的,因为.NET 2.0在我的回答时已经出了三年多了,它包含对Regex类的更改,这些更改会显着影响静态方法和实例方法之间的差异。

在.NET 2.0(和4.0)中,静态IsMatch函数定义如下:

public static bool IsMatch(string input, string pattern){
    return new Regex(pattern, RegexOptions.None, true).IsMatch(input);
}

这里的显着差异是第三个参数很少true。这对应于名为“useCache”的参数。如果是这样,则在第二次和后续使用时从缓存中检索解析的树。

这种缓存会占用静态和实例方法之间的大多数但不是全部的性能差异。在我的测试中,静态IsMatch方法仍然比实例方法慢约20%,但是当在一组10,000个输入字符串上运行100次时,这只增加了大约半秒(总共1个)百万次行动)。

在某些情况下,这20%的减速仍然很重要。如果你发现自己正在重复数亿个字符串,你可能希望尽一切可能使它更有效率。但我敢打赌99%的时间,你使用特定的正则表达不会超过几次,并且你输入静态方法的额外毫秒甚至不会显而易见。

支持devgeezer的道具,大约在一年前就指出了这一点,尽管似乎没人注意到。

我的老答案如下:


静态IsMatch函数定义如下:

public static bool IsMatch(string input, string pattern){
    return new Regex(pattern).IsMatch(input);
}

而且,是的,初始化Regex对象并非易事。您应该使用静态IsMatch(或任何其他静态Regex函数)作为仅使用一次的模式的快捷方式。如果您将重用该模式,那么重用Regex对象也是值得的。

至于你是否应该按照Jon Skeet的建议指定RegexOptions.Compiled,这是另一个故事。答案是:它取决于。对于简单模式或仅使用少数几次的模式,使用非编译实例可能会更快。在决定之前你一定要做好准备。编译正则表达式对象的成本确实非常大,可能不值得。


举例来说,以下内容:

const int count = 10000;

string pattern = "^[a-z]+[0-9]+$";
string input   = "abc123";

Stopwatch sw = Stopwatch.StartNew();
for(int i = 0; i < count; i++)
    Regex.IsMatch(input, pattern);
Console.WriteLine("static took {0} seconds.", sw.Elapsed.TotalSeconds);

sw.Reset();
sw.Start();
Regex rx = new Regex(pattern);
for(int i = 0; i < count; i++)
    rx.IsMatch(input);
Console.WriteLine("instance took {0} seconds.", sw.Elapsed.TotalSeconds);

sw.Reset();
sw.Start();
rx = new Regex(pattern, RegexOptions.Compiled);
for(int i = 0; i < count; i++)
    rx.IsMatch(input);
Console.WriteLine("compiled took {0} seconds.", sw.Elapsed.TotalSeconds);

如所列出的count = 10000,第二个输出最快。将count增加到100000,并且已编译的版本获胜。

答案 1 :(得分:31)

如果您要多次重复使用正则表达式,我将使用RegexOptions.Compiled创建它并缓存它。没有必要让框架在每次需要时解析正则表达式模式。

答案 2 :(得分:11)

对于我机器上的.NET版本,这个答案已不再正确。 4.0.30319&amp; 2.0.50727对IsMatch都有以下内容:

public static bool IsMatch(string input, string pattern)
{
  return new Regex(pattern, RegexOptions.None, true).IsMatch(input);
}

'true'值用于名为“useCache”的构造函数参数。所有的正则表达式构造函数最终都是通过这个构造函数链接的,静态函数直接称之为 - 传递给'true'。

您在BCL博客文章中了解了有关优化正则表达式性能的更多信息,重点介绍了静态方法的缓存使用here。此博客文章还引用了性能测量。阅读一系列关于优化Regex性能的博客文章是一个很好的起点。

答案 3 :(得分:8)

我同意Jon的意见,只是为了澄清它看起来像这样:

static Regex regex = new Regex("regex", RegexOptions.Compiled);

对于其他有时可能有用的标志,还值得查看RegexOptions枚举。

答案 4 :(得分:6)

有许多因素会影响使用正则表达式的性能。最终,找出最适合您情况的唯一方法是使用尽可能真实的情况进行衡量。

MSDN上正则表达式对象compilation and reuse上的页面涵盖了这一点。总之,它说

  1. 编译后的正则表达式需要一些时间来编译,一旦编译,只会在AppDomain卸载时释放内存。是否应该使用编译取决于您使用的模式数量以及使用频率。

  2. 静态Regex方法缓存最后15个(默认情况下)模式的已解析正则表达式表示。因此,如果您没有在应用程序中使用许多不同的模式,或者您的使用已经充分聚集,那么缓存实例或缓存它的框架之间没有太大区别。

答案 5 :(得分:3)

我建议您在编译正则表达式时阅读Jeff's post

至于问题,如果你问这个问题,这意味着你只会使用它一次。因此,反射器对Regex.IsMatch的反汇编是真的无所谓:

public static bool IsMatch(string input, string pattern, RegexOptions options)
{
    return new Regex(pattern, options, true).IsMatch(input);
}

答案 6 :(得分:2)

对于我正在研究的WinForm应用程序,我们可以定义一个有效字符的正则表达式,它可以在每次击键时运行,并对任何文本框(数据输入应用程序)的文本进行验证,所以我使用了缓存或编译的正则表达式,如

  private static Dictionary<string, Regex> regexCache = new Dictionary<string, Regex>(20);

正则表达式是关键。

然后我在验证数据时可以调用静态函数:

public static bool RegExValidate(string text, string regex)
{
  if (!regexCache.ContainsKey(regex))
  {
    Regex compiledRegex = new Regex(regex,RegexOptions.Compiled);
    regexCache.Add(regex, compiledRegex);
  }
  return regexCache[regex].IsMatch(text);
}