对于在分隔符之间匹配文本的常见问题(例如<
和>
),有两种常见的模式:
*
形式的贪婪+
或START [^END]* END
量词,例如<[^>]*>
或*?
形式的惰性+?
或START .*? END
量词,例如<.*?>
。是否有一个特别的理由支持一个而不是另一个?
答案 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!/
这些正则表达式不仅不会不必要地回溯,而且不必保存使回溯成为可能的状态信息。