在分隔符之间匹配文本:贪婪或懒惰的正则表达式?

时间:2011-08-29 08:12:38

标签: regex language-agnostic greedy regex-greedy

对于在分隔符之间匹配文本的常见问题(例如<>),有两种常见的模式:

  • 使用*形式的贪婪+START [^END]* END量词,例如<[^>]*>
  • 使用*?形式的惰性+?START .*? END量词,例如<.*?>

是否有一个特别的理由支持一个而不是另一个?

3 个答案:

答案 0 :(得分:12)

一些优点:

[^>]*

  • 更具表现力。
  • 无论/s标志如何,都会捕获换行符。
  • 考虑得更快,因为引擎不必回溯以找到成功匹配(使用[^>]引擎无法做出选择 - 我们只给出一种方法来匹配模式与字符串)。

.*?

  • 没有“代码重复” - 结束字符只出现一次。
  • 在结束定界符超过字符长度的情况下更简单。 (在这种情况下,字符类不起作用)常见的替代方法是(?:(?!END).)*。如果END定界符是另一种模式,则更糟糕。

答案 1 :(得分:7)

第一个更明确,我。即它绝对排除了结束分隔符成为匹配文本的一部分。在第二种情况下不保证这一点(如果正则表达式扩展为不仅仅匹配此标记)。

示例:如果您尝试将<tag1><tag2>Hello!<.*?>Hello!匹配,则正则表达式将匹配

<tag1><tag2>Hello!

<[^>]*>Hello!将匹配

<tag2>Hello!

答案 2 :(得分:6)

当接近这样的问题时,大多数人都没有考虑到当正则表达式无法找到匹配时会发生什么。 那是当杀手性能下沉最有可能出现时。例如,以蒂姆的例子为例,你正在寻找像<tag>Hello!这样的东西。考虑一下:

<.*?>Hello!

正则表达式引擎找到<并快速找到结束>,但不是>Hello!。因此,.*?继续寻找{{1>} ,然后是>。如果没有,它会在文件放弃之前一直到文档的末尾。然后正则表达式引擎继续扫描,直到找到另一个Hello!,并再次尝试。 我们已经知道结果如何,但正则表达式引擎通常不会;它与文档中的每个<一起经历了相同的rigamarole。现在考虑另一个正则表达式:

<

与以前一样,它会从<[^>]*>Hello! 快速匹配到<,但无法匹配>。它会回溯到Hello!,然后退出并开始扫描另一个<。它仍将像第一个正则表达式那样检查每个<,但每次找到它时都不会一直搜索到文档的末尾。

但情况甚至更糟。如果你仔细想想,<实际上相当于消极的前瞻。它说“在使用下一个字符之前,请确保正则表达式的其余部分在此位置不匹配。”换句话说,

.*?

......相当于:

/<.*?>Hello!/

因此,在您执行的每个位置,不仅仅是正常的匹配尝试,而是更昂贵的前瞻。 (它至少要贵两倍,因为前瞻必须扫描至少一个字符,然后/<(?:(?!>Hello!).)*(?:>Hello!|\z(*FAIL))/ 继续前进并消耗一个字符。)

.是Perl的backtracking-control verbs之一(PHP也支持)。(*FAIL)表示“或到达文档的末尾并放弃”。)

最后,否定字符类方法的另一个优点。虽然它没有(正如@Bart指出的那样)量词就像占有欲一样,如果你的味道支持它,没有什么能阻止你制造它占有欲:

|\z(*FAIL)

...或将其包装在原子组中:

/<[^>]*+>Hello!/

这些正则表达式不仅不会不必要地回溯,而且不必保存使回溯成为可能的状态信息。