Regex 为什么正则表达式。*在一个位置较慢,在另一个位置较快

Regex 为什么正则表达式。*在一个位置较慢,在另一个位置较快,regex,Regex,最近我在java/groovy中使用了很多正则表达式。对于测试,我经常使用。显然,我也在关注正则表达式的性能 我注意到,正确使用*可以显著提高总体性能。基本上,在正则表达式的末尾使用*,或者更好的说法是不在正则表达式的末尾使用,这会降低性能 例如,在正则表达式中,所需的步骤数为27: ,将所需的步骤大幅减少到16个: 但是,它不会进一步减少步骤: 我有几个问题: 为什么会出现上述情况?我不想比较\s和*。我知道区别。我想知道为什么\s和*的成本根据它们在完整正则表达式中的位置而不同。然后是

最近我在java/groovy中使用了很多正则表达式。对于测试,我经常使用。显然,我也在关注正则表达式的性能

我注意到,正确使用
*
可以显著提高总体性能。基本上,在正则表达式的末尾使用
*
,或者更好的说法是不在正则表达式的末尾使用,这会降低性能

例如,在正则表达式中,所需的步骤数为27:

,将所需的步骤大幅减少到16个:

但是,它不会进一步减少步骤:

我有几个问题:

  • 为什么会出现上述情况?我不想比较
    \s
    *
    。我知道区别。我想知道为什么
    \s
    *
    的成本根据它们在完整正则表达式中的位置而不同。然后是正则表达式的特征,其成本可能根据其在整个正则表达式中的位置而有所不同(或者根据位置以外的任何其他方面,如果有)
  • 该站点中给出的步骤计数器是否真的给出了关于regex性能的任何指示
  • 您还有哪些其他简单或类似(与职位相关)的正则表达式性能观察结果

  • 以下是调试器的输出

    性能差异的主要原因是
    *
    将消耗所有内容,直到字符串结束(除了换行符)。然后该模式将继续,强制正则表达式回溯(如第一幅图所示)

    \s
    *
    在模式结束时表现同样出色的原因是,如果没有其他匹配项(除了WS),贪婪模式与使用空格没有区别

    如果您的测试字符串没有以空格结尾,那么性能就会有所不同,就像您在第一个模式中看到的那样——正则表达式将被迫回溯

    编辑

    如果以空格以外的内容结束,则可以看到性能差异:

    坏的:

    ^myname.*mahesh.*hiworld

    更好:

    ^myname.*mahesh\s*hiworld

    更好的是:

    ^myname\s*mahesh\s*hiworld


    正则表达式引擎使用
    *
    量词(又称贪婪量词)的方式是消耗输入中匹配的所有内容,然后:

  • 试试正则表达式中的下一个术语。如果匹配,则继续
  • “取消使用”一个字符(将指针向后移动一个),也称为回溯并转到步骤1
  • 由于
    匹配任何内容(几乎),遇到
    *
    后的第一个状态是将指针移到输入端,然后开始在输入端一次移动一个字符,尝试下一个术语,直到匹配为止

    使用
    \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关于正则表达式实现的注释。