不匹配时正则表达式灾难性回溯

Regex catastrophic backtracking when not match

在进行任何编码之前,我正在 https://regex101.com/ 中测试我的正则表达式。

正则表达式:

\[(.*?)\]((?:.\s*)*?)\[\/\]

示例字符串:

[tag1]Test's Text Test Text Test Text Test Text.

Test Text Test Text Test "Text Test Text" Test Text Test Text.

Test Text? Test Text Test Text Test Text Test Text Test Text Test Text.

Test Text, Test Text Test Text Test Text Test Text Test Text Test Text Test Text.[/tag1]

[tag2]Test's Text Test Text Test Text Test Text.

Test Text Test Text Test "Text Test Text" Test Text Test Text.

Test Text? Test Text Test Text Test Text Test Text Test Text Test Text.

Test Text, Test Text Test Text Test Text Test Text Test Text Test Text Test Text.[/tag2]

....

....

我正在尝试在一些长字符串中捕获 2 组。第一个是方括号内的文字,第二个是标签内的文字。

上面的正则表达式和字符串在正则表达式匹配时没有任何问题。如果匹配,每次匹配的步数仅为 1000+。但是如果开始和结束标签不匹配,就会发生灾难性的回溯并且匹配在 126.000+ 步中完成并停止寻找其他匹配字符串。

我知道,要防止回溯问题就是避免使用具有“+”或“*”的嵌套结构,但我似乎不知道有什么更好的方法来做到这一点。

也许有人可以提供或建议比我的更好的正则表达式?

我认为灾难性回溯的出现是因为这种模式:

(?:.\s*)*?

在可以重复的组中嵌套重复总是给正则表达式引擎带来痛苦。查看您的正则表达式图表,很明显您的模式正在产生丑陋的开销:

您可以改进您的正则表达式,使其具有如下模式:

\[(.*?)\](.*?)\[\/\]        // Using single line flag
(?s)\[(.*?)\](.*?)\[\/\]    // Using inline single line flag

Working demo

另外,如果你不想使用单行标志,你可以使用这样的小技巧:

\[(.*?)\]([\s\S]*?)\[\/\]

此外,您可能会发现使用 +(1 个或多个)运算符而不是 *(0 个或多个)运算符很有用:

\[(.+?)\](.+?)\[\/\]

显然,这个正则表达式:\[([^\]]+)\]([^\[]+)\[\/\] 的步骤要少得多。谢谢大家的回答。

Demo

前言

首先,使用合适的测试环境。如果您在 .NET 中使用正则表达式,请不要在不支持 .NET 正则表达式的正则表达式测试器上对其进行测试。

Regex101.com 不支持 .NET 正则表达式!

您的正则表达式模式不会对您在 RegexStorm.net.

发布的字符串造成任何灾难性回溯

根本原因

好的,正则表达式模式非常糟糕且效率低下。为什么? (?:.\s*)*?(包含在一些更大的模式中,作为它自己,独立的,这不会是一个问题),匹配任何字符后跟零个或多个(因此,可选的)空格,所有这些重复 0 次或多次,但越少越好。所以,.\s* 都可以匹配同一个字符串。当您将其包装在一个组中并添加量词时,正则表达式引擎尝试的可能匹配组合的总数呈指数增长。

增强模式

增强不是很明显,但许多人会提供类似 Federico 给出的解决方案:使用惰性点匹配模式。所以,(?s)\[([^]]*)](.*?)\[/] (demo) looks a viable solution. It yields 7,843 iterations per second at RegexHero.net.

使用展开循环方法,我们可以根据输入将正则表达式性能提高 n 倍。在这里,我们可以将 .*? 子模式写为任何字符,但 [ 和任何 [ 后跟 /] 直到 \[/]。这可以用否定字符 类 和 1 个量化组内的前瞻来编写(它甚至不需要任何修饰符或标志):

\[([^]]*)]([^[]*(?:\[(?!/])[^[]*)*)\[/]

参见 this RegexStorm demo。此正则表达式模式每秒产生 114,225 次迭代。这是因为 [tag1][/tag1] 之间根本没有 [,如果一个字符串包含很多 [ 或者只包含 [=16],性能会变差=](这在现实生活中不应该发生)。

测试

这是 RegexHero 测试:

您的原始正则表达式在该站点上仅产生 5,094 个 ips。