Regex 为什么正则表达式。*在一个位置较慢,在另一个位置较快
最近我在java/groovy中使用了很多正则表达式。对于测试,我经常使用。显然,我也在关注正则表达式的性能 我注意到,正确使用Regex 为什么正则表达式。*在一个位置较慢,在另一个位置较快,regex,Regex,最近我在java/groovy中使用了很多正则表达式。对于测试,我经常使用。显然,我也在关注正则表达式的性能 我注意到,正确使用*可以显著提高总体性能。基本上,在正则表达式的末尾使用*,或者更好的说法是不在正则表达式的末尾使用,这会降低性能 例如,在正则表达式中,所需的步骤数为27: ,将所需的步骤大幅减少到16个: 但是,它不会进一步减少步骤: 我有几个问题: 为什么会出现上述情况?我不想比较\s和*。我知道区别。我想知道为什么\s和*的成本根据它们在完整正则表达式中的位置而不同。然后是
*
可以显著提高总体性能。基本上,在正则表达式的末尾使用*
,或者更好的说法是不在正则表达式的末尾使用,这会降低性能
例如,在正则表达式中,所需的步骤数为27:
,将所需的步骤大幅减少到16个:
但是,它不会进一步减少步骤:
我有几个问题:
\s
和*
。我知道区别。我想知道为什么\s
和*
的成本根据它们在完整正则表达式中的位置而不同。然后是正则表达式的特征,其成本可能根据其在整个正则表达式中的位置而有所不同(或者根据位置以外的任何其他方面,如果有)李>
以下是调试器的输出 性能差异的主要原因是
*
将消耗所有内容,直到字符串结束(除了换行符)。然后该模式将继续,强制正则表达式回溯(如第一幅图所示)
\s
和*
在模式结束时表现同样出色的原因是,如果没有其他匹配项(除了WS),贪婪模式与使用空格没有区别
如果您的测试字符串没有以空格结尾,那么性能就会有所不同,就像您在第一个模式中看到的那样——正则表达式将被迫回溯
编辑
如果以空格以外的内容结束,则可以看到性能差异:
坏的:
^myname.*mahesh.*hiworld
更好:
^myname.*mahesh\s*hiworld
更好的是:
^myname\s*mahesh\s*hiworld
正则表达式引擎使用
*
量词(又称贪婪量词)的方式是消耗输入中匹配的所有内容,然后:
匹配任何内容(几乎),遇到*
后的第一个状态是将指针移到输入端,然后开始在输入端一次移动一个字符,尝试下一个术语,直到匹配为止
使用\s*
,只使用空格,因此指针最初会精确地移动到您希望的位置-无需回溯以匹配下一个术语
您应该尝试使用不情愿的量词*?
,它将一次消耗一个字符,直到下一个词匹配为止,它的时间复杂度应与\s*
相同,但效率稍高一些,因为不需要检查当前字符
表达式末尾的
\s*
和*
将执行类似的操作,因为这两个表达式都将消耗f输入端匹配的所有内容,这使得两个表达式的指针位置相同。您应该阅读有关回溯的更多信息
匹配m
,a
等,而\s
不匹配(它只匹配空格)。最后,\s*
和*
在这里的工作原理相同。我不知道这类问题是否离题。@Stribizev我认为OP理解
的意思。问题是为什么\s
在条件上性能更好。请使用regex101的调试器查看发生了什么。概括:可以相互匹配的量化子模式不应该一个接一个地出现。请注意,在您的示例中,最后的*
和\s*
都是无用的。无论发生什么情况,它们都将始终匹配。虽然“*?”比“*”发生可怕回溯的可能性更低,但如果输入与模式不匹配,它仍然会导致严重的回溯。如果回溯缓冲区溢出,则带有“*?
的regexp仍会导致问题。我见过很多次。惰性匹配不会“修复”任何东西,正则表达式模式必须是“线性”的才能有效。请注意,正则表达式引擎可能对特定的子模式进行了专门的优化<代码>*作为一种组合,很常见,足以证明这种优化是合理的。对于给定的示例<代码>^myname.*mahesh,*
将尝试使用实际需要的m
来匹配mahesh
。智能正则表达式引擎可以存储m
的位置,并在单个步骤中回溯。看看erip的回答,其中显示了正式的步骤。缩短回溯的正则表达式引擎需要18个步骤,而不是28个步骤。请注意,在某些情况下,非贪婪限定符也可能导致额外的回溯。考虑<代码> A*BC/<代码> VS <代码> A*BC <代码> >代码> ABABAB/<代码>注意,正则表达式转换为DFAs()的ReGEX引擎不做任何回溯,总是很快,尽管它们往往不支持一些功能,如反向引用。只是一个旁注:如果需要回溯,那么实现就有问题。常用的参考资料是Russ Cox关于正则表达式实现的注释。