为什么这种模式在java中需要很长时间才能匹配?

时间:2011-09-21 14:42:14

标签: java regex

以下正则表达式模式应用于非常长的字符串(60KB)时,会导致java看起来“挂起”。

.*\Q|\E.*\Q|\E.*\Q|\E.*bundle.*

我不明白为什么。

3 个答案:

答案 0 :(得分:5)

你在那个正则表达式中有5个急切/贪婪的量词,所以你很可能会做大量的回溯。

This article详细解释了这种行为,并提出了改善正则表达式效果的建议。

在这种情况下,答案可能是将贪婪量词替换为非贪婪量词,或者更好地使用非回溯子模式。

答案 1 :(得分:4)

首先,我认为你可以简化你的正则表达式如下:

.*\Q|\E.*\Q|\E.*\Q|\E.*bundle.*

可能会成为

.*\|.*\|.*\|.*bundle.*

其次,为了回答你的问题,你的正则表达式中有这么多“。*”的事实意味着正则表达式解决方案必须有一些可能性。如果这适用于您的情况,以下可能会更快。

(?:[^\|]*\|){3}[^|]*bundle.*

通过将“。”更改为“[^ |] ”,您可以缩小引擎的焦点,因为第一个“。*”不会急切地抓住第一个“|”。

答案 2 :(得分:4)

基本上,“。*”(匹配任意数量的任何数字)意味着尝试匹配整个字符串,如果它不匹配,则返回并再试一次,等等使用其中一个并不是太多一个问题,但使用多个所需的时间呈指数增长。这是对这类事情的一个相当深入(并且更加准确)的讨论:http://discovery.bmc.com/confluence/display/Configipedia/Writing+Efficient+Regex

编辑:(我希望你真的想知道为什么)

示例源字符串:

aaffg,  ";;p[p09978|ksjfsadfas|2936827634876|2345.4564a bundle of sticks

一种看待它的方式:

该过程需要很长时间,因为.*匹配整个源字符串(aaffg, ";;p[p09978|ksjfsadfas|2936827634876|2345.4564a bundle of sticks),只是发现它不以|符号结尾,然后回溯到最后一个|符号(...4876|2345...)的大小写,然后尝试将下一个.*一直匹配到字符串的结尾。

它开始寻找表达式中指定的下一个|符号,但未找到它,然后回溯到匹配的第一个|符号(...4876|2345...中的符号) ,丢弃匹配并在其前面找到最接近的|...dfas|2936...),以便它能匹配匹配表达式中的第二个|符号。

然后,它会继续将.*2936827634876和第二个|...4876|2345...中的.*以及下一个|的{​​{1}}相匹配,只是发现你想要另一个.*\Q|\E.*\Q|\E.*\Q|\E.*bundle.* 。然后它会一次又一次地继续回溯,直到它匹配您指定的所有符号。

另一种看待它的方式:

(原始表达):

match:
               any number of anything, 
followed by    a single '|', 
followed by    any number of anything, 
followed by    a single '|', 
followed by    any number of anything, 
followed by    a single '|', 
followed by    any number of anything,
followed by    the literal string 'bundle',
followed by    any number of anything

这大致转化为

any number of anything

问题是|包含any number of anything that is not a '|'符号,需要一遍又一遍地解析整个字符串,其中你的意思是.*

为了修复或改进表达式,我建议三件事:

首先(也是最重要的),将大部分“匹配任何东西”([^|])替换为否定字符类([^|]*\Q|\E[^|]*\Q|\E[^|]*\Q|\E.*bundle.* ),如下所示:

|

...这会阻止它一遍又一遍地匹配字符串的结尾,而是将所有非|符号匹配到第一个不是“不是{{{ 1}}符号“(双重否定意味着直到第一个|符号),然后匹配|符号,然后转到下一个等等...

第二次更改(有些重要,取决于您的源字符串)应该是倒数第二次“将任意数量的任何数字匹配”(.*)变为“懒惰” “或”不情愿“类型的”任意数量的“(.*?)。这将使其尝试匹配任何东西,以寻找bundle而不是跳过bundle并匹配其余字符串,只是意识到一旦它到达那里就有更多匹配,不得不回溯。这将导致:

[^|]*\Q|\E[^|]*\Q|\E[^|]*\Q|\E.*?bundle.*

我建议的第三个更改是为了提高可读性 - 将\Q\E块替换为单个转义符,如\|中所示,如下所示:

[^|]*\|[^|]*\|[^|]*\|[^|].*?bundle.*

这就是表达式在内部处理的方式 - 实际上有一个函数将表达式转换为“转义\ Q和\ E之间的所有特殊字符” - \Q\E只是一个简写,如果它不会使你的表达更短或更容易阅读,不应该使用它。周期。

否定的字符类有一个未转义的|因为|不是字符类上下文中的特殊字符 - 但是不要过分偏离。如果你愿意,你可以逃脱它们,但你不必这样做。

最终表达式大致翻译为:

match:
               any number of anything that is not a '|', 
followed by    a single '|', 
followed by    any number of anything that is not a '|', 
followed by    a single '|', 
followed by    any number of anything that is not a '|', 
followed by    a single '|', 
followed by    any number of anything, up until the next expression can be matched,
followed by    the literal string 'bundle',
followed by    any number of anything

我使用的一个好工具(但要花一些钱)称为RegexBuddy - 用于理解正则表达式的伴侣/免费网站是http://www.regular-expressions.info,解释重复的特定页面是http://www.regular-expressions.info/repeat.html

RegexBuddy模仿其他正则表达式引擎,并说你的原始正则表达式需要544'步骤'来匹配,而不是我提供的版本的35'步骤'。

SLIGHTLY LONGER示例源字符串A:

aaffg,  ";;p[p09978|ksjfsadfas|12936827634876|2345.4564a bundle of sticks

SLIGHTLY LONGER示例源字符串B:

aaffg,  ";;p[p09978|ksjfsadfas|2936827634876|2345.4564a bundle of sticks4me

较长的源字符串'A'(在1之前添加2936827634876)不影响我建议的替换,但是将原始字符串增加了6个步骤

更长的源字符串'B'(在表达式末尾添加'4me')再次影响我建议的替换,但是将 48 步骤添加到原始

因此,根据字符串与上述示例的不同,60K字符串只需要544步,或者可能需要超过一百万步