为什么这个正则表达式在某些查询中如此缓慢?



对于一个数据迁移项目,我需要对一长串英语短句的格式进行基本验证。

由于某些原因,一些特定的字符串匹配非常慢(在我的笔记本电脑上高达90秒)。

我最终从正则表达式得到了预期的结果,但是我很好奇为什么这个正则表达式这么慢。我自己想不出来。

^((()?[0-9a-zäàâèéêçôóû']+()(s|$)|s|-|.|s?/s?|!|?)?)+$ IgnoreCase|Compiled

(运行在。net 4.5/c#上)

查询:

longword longword longword longword ‘a’ and ‘b’

longword longword (longword longword).

对于较短的字符串,它以正常速度工作。在上面的例子中用word代替longword会加快很多。从第二个示例中删除.也将使其工作。加几个longwords,它可以运行一整天。

特别是第一个例子让我困惑,因为‘’字符甚至不允许在正则表达式中

regex失败的速度很慢,因为它包含嵌套的量词,由于它们创建了大量的匹配可能性,可能会导致过度耗时的回溯。

过度回溯的罪魁祸首通常是具有灵活量词的相邻子模式,它们可以匹配相同的子字符串,嵌套量词就是这样的一个例子。

考虑正则表达式(w+)+可以用8种不同的方式匹配字符串"word"

word            w{4}
w-ord           w{1} w{3}
wo-rd           w{2} w{2}
wor-d           w{3} w{1}
wo-r-d          w{2} w{1} w{1}
w-or-d          w{1} w{2} w{1}
w-o-rd          w{1} w{1} w{2}
w-o-r-d         w{1} w{1} w{1} w{1}

和字符串"longword"以128种方式匹配,"verylongword"以2048种方式匹配,很快就清楚了,模式匹配字符串的可能方式的数量随着字符串的长度呈指数增长:

Math.Pow(2, string.Length - 1) 

这就是为什么"在上面的例子中用word代替longword会大大加快速度"

你的正则表达式比上面的复杂得多,所以如果第一个不匹配的字符以某种方式出现在字符串中,正则表达式引擎将不得不回溯并尝试大量的替代方法来匹配字符串,直到它可以确定完全匹配是不可能的。

在您的正则表达式中没有任何内容可以匹配示例字符串中的).‘a’,因此正则表达式将失败-但是它将花费很长时间,因为不匹配的字符出现在字符串的末尾。

可以通过尝试匹配例如字符串

来确认嵌套的量词是有问题的
"longword longword longword!"

使用明显简单的模式

^([a-zs]+)+$

在我的机器上,引擎找不到匹配项需要十秒钟以上的时间。
如果可选的s?随后被添加- ^([a-zs]+s?)+$ -花费的时间加倍。

你的正则表达式有超过十种不同的可选?替代方案,在每次匹配主要角色类别[]之后考虑,这将类似地加剧回溯。

解决方案是通过使外部()中的子模式成为原子组来防止引擎回溯到与之匹配的子模式。这可以通过在开头的(后面添加?>来实现。

@"^(?>(()?[0-9a-zäàâèéêçôóû']+()(s|$)|s|-|.|s?/s?|!|?)?)+$"

或者等价的,但可能更有效的

@"^(?>(?[a-z0-9äàâèéêçôóû']+(?:[s./!?-]|)(?:s|$))?)+$"

请注意,这仍然不允许).)!等,您可能想要这样做。

参见优化正则表达式性能,第二部分:负责回溯

最新更新