正则表达式IsMatch执行时间太长

时间:2013-08-09 15:02:15

标签: c# .net regex

我在使用RegEx的.NET项目中遇到了一个奇怪的问题。请参阅下面的C#代码:

const string PATTERN = @"^[a-zA-Z]([-\s\.a-zA-Z]*('(?!'))?[-\s\.a-zA-Z]*)*$";
const string VALUE = "Ingebrigtsen Myre (Øvre)";
System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(PATTERN);
if (!regex.IsMatch(VALUE)) // <--- Infinite loop here
     return string.Empty;
// Some other code

我使用此模式验证所有类型的名称(拳头名称,姓氏,中间名称等)。值是一个参数,但我提供它作为上面的常量,因为问题不经常复制 - 只有特殊符号:*,(,)等(对不起,但我没有这些符号的完整列表)

你能帮我解决这个无限循环吗?谢谢你的帮助。

补充说:这段代码放在项目的基础层面上,我不想在那里进行任何重构 - 我只想快速解决这个问题。

补充2:我知道技术上不是循环 - 我的意思是“regex.IsMatch(VALUE)”永远不会结束。我等了大约一个小时,它还在执行。

3 个答案:

答案 0 :(得分:3)

你的非平凡正则表达式:^[a-zA-Z]([-\s\.a-zA-Z]*('(?!'))?[-\s\.a-zA-Z]*)*$,最好用自由间隔模式中的注释编写,如下所示:

Regex re_orig = new Regex(@"
    ^                 # Anchor to start of string.
    [a-zA-Z]          # First char must be letter.
    (                 # $1: Zero or more additional parts.
      [-\s\.a-zA-Z]*  # Zero or more valid name chars.
      (               # $2: optional quote.
        '             # Allow quote but only
        (?!')         # if not followed by quote.
      )?              # End $2: optional quote.
      [-\s\.a-zA-Z]*  # Zero or more valid name chars.
    )*                # End $1: Zero or more additional parts.
    $                 # Anchor to end of string.
    ",RegexOptions.IgnorePatternWhitespace);

在英语中,这个正则表达式基本上是这样的:“匹配以字母[a-zA-Z]开头的字符串,后跟零个或多个字母,空格,句点,连字符或单引号,但每个单引号可能不会立即跟随另一个单引号。“

请注意,您的上述正则表达式允许使用奇怪的名称,例如:"ABC---...'... -.-.XYZ ",这可能是您需要的,也可能不是。它还允许多行输入和以空格结尾的字符串。

上述正则表达式的“无限循环”问题是当此正则表达式应用于一行中包含两个单引号的长无效输入时,会出现catastrophic backtracking。这是一个相同的模式,匹配(并且不匹配)完全相同的字符串,但不会遇到灾难性的回溯:

Regex re_fixed = new Regex(@"
    ^                # Anchor to start of string.
    [a-zA-Z]         # First char must be letter.
    [-\s.a-zA-Z]*    # Zero or more valid name chars.
    (?:              # Zero or more isolated single quotes.
      '              # Allow single quote but only
      (?!')          # if not followed by single quote.
      [-\s.a-zA-Z]*  # Zero or more valid name chars.
    )*               # Zero or more isolated single quotes.
    $                # Anchor to end of string.
    ",RegexOptions.IgnorePatternWhitespace);

在这里,它在您的代码上下文中是简短的形式:

const string PATTERN = @"^[a-zA-Z][-\s.a-zA-Z]*(?:'(?!')[-\s.a-zA-Z]*)*$";

答案 1 :(得分:1)

看看你的正则表达式的这一部分:

( [-\s\.a-zA-Z]* ('(?!'))? [-\s\.a-zA-Z]* )*$
^              ^         ^              ^  ^ 
|              |         |              |  |
|              |         |              |  This group repeats any number of times
|              |         |              charclass repeats any number of times
|              |         This group is optional
|              This character class also repeats any number of times
Outer group (repeated, as seen above)

这意味着只要您的输入字符串包含不在字符类中的字符(如示例中的括号和非ASCII字母),就会在许多排列中尝试前面的字符,这些排列的数量会以指数方式增加用字符串的长度。

为避免这种情况(并允许正则表达式更快失败,请使用atomic groups

const string PATTERN = @"^[a-zA-Z](?>(?>[-\s\.a-zA-Z]*)(?>'(?!'))?(?>[-\s\.a-zA-Z])*)*$";

答案 2 :(得分:0)

这里有“任意数量的任意数字”:

 ...[-\s\.a-zA-Z]*)*

并且由于您的输入没有匹配,引擎会回溯以尝试将输入分开的所有排列,并且尝试次数会随着输入的长度呈指数级增长。

您可以通过添加“+”来修复它,以便生成possessive quantifier,一旦消费将回溯以找到其他组合:

const string PATTERN = @"^[a-zA-Z]([-\s\.a-zA-Z]*('(?!'))?[-\s\.a-zA-Z]*+)*$";
                                                                        ^-- added + here

你可以看到一个live demo(在rubular上),证明添加加解决了循环问题,并且仍然匹配没有奇数字符的输入。