对于一个数据迁移项目,我需要对一长串英语短句的格式进行基本验证。
由于某些原因,一些特定的字符串匹配非常慢(在我的笔记本电脑上高达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|$))?)+$"
请注意,这仍然不允许).
或)!
等,您可能想要这样做。
参见优化正则表达式性能,第二部分:负责回溯