为什么正则表达式。*在一个地方较慢而在另一个地方较快

时间:2015-11-06 13:44:27

标签: regex

最近我在java / groovy中使用了很多正则表达式。为了测试,我经常使用regex101.com。显然我也在看正则表达式的表现。

有一点我注意到正确使用.*可以显着提高整体性能。首先,在中间使用.*,或者更好地说不在正则表达式的末尾是性能杀戮。

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

enter image description here

If I change first .* to \s*,它会将所需的步骤显着减少到16:

enter image description here

然而,if I change second .* to \s*,它不会进一步减少步骤:

enter image description here

我几乎没有问题:

  1. 为什么以上? 我不想比较\s.*。我知道不同之处。我想知道为什么\s.*根据他们在完整正则表达式中的位置而有所不同。然后正则表达式的特征可能会根据它们在整体正则表达式中的位置而有所不同(或者基于位置以外的任何其他方面,如果有的话)。
  2. 此站点中给出的步骤计数器是否真的给出了有关正则表达式性能的任何指示?
  3. 你有哪些其他简单或类似(位置相关)的正则表达式性能观察结果?

2 个答案:

答案 0 :(得分:26)

以下是调试器的输出。

pattern 1

pattern 2

pattern 3

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

\s.*在模式结束时表现同样良好的原因是贪婪模式与消耗空白没有区别,如果没有别的东西可以匹配(除此之外) WS)。

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

修改

如果你用空格以外的东西结束,你可以看到性能差异:

为:

^myname.*mahesh.*hiworld

bad

更好:

^myname.*mahesh\s*hiworld

little better

更好:

^myname\s*mahesh\s*hiworld

Much better

答案 1 :(得分:21)

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

  1. 在正则表达式中尝试下一个术语。如果匹配,请继续
  2. " unconsume"一个字符(将指针移回一个),又名回溯并转到第1步。
  3. 由于.匹配任何东西(差不多),遇到.*后的第一个状态是将指针移动到输入的末尾,然后开始通过输入一个字符移回一次尝试下一个学期,直到那场比赛。

    使用\s*时,只消耗空格,因此指针最初会准确移动到您想要的位置 - 无需回溯以匹配下一个术语。

    您应该尝试的是使用不情愿的量词.*?,它将一次消耗一个字符,直到下一个字词匹配,其时间复杂度应与{{1但是效率稍高,因为不需要检查当前的char。

    表达式的 end 中的

    \s*\s*将执行类似的操作,因为两者都将消耗匹配的结尾f输入处的所有内容,这使指针保持不变两种表达方式的位置相同。