针对匹配定界符(例如<
和>
)之间的文本的常见问题,有两种常用模式:
- 使用贪婪的
*
或+
量词并采用形式为START [^END]* END
的表示方式,例如<[^>]*>
,或 - 使用懒惰的
*?
或+?
量词并采用形式为START .*? END
的表示方式,例如<.*?>
。
是否有特殊理由支持其中一种而不是另一种?
针对匹配定界符(例如<
和>
)之间的文本的常见问题,有两种常用模式:
*
或 +
量词并采用形式为START [^END]* END
的表示方式,例如 <[^>]*>
,或*?
或 +?
量词并采用形式为START .*? END
的表示方式,例如 <.*?>
。是否有特殊理由支持其中一种而不是另一种?
一些优点:
[^>]*
:
/s
标志,都可以捕获换行符。[^>]
时,引擎不需要做任何选择——我们只提供了一种匹配模式来针对字符串进行匹配)。.*?
(?:(?!END).)*
。如果 END 定界符是另一个模式,则情况会变得更糟。<.*?>Hello!
匹配<tag1><tag2>Hello!
,则正则表达式将匹配。
<tag1><tag2>Hello!
而<[^>]*>Hello!
将匹配
<tag2>Hello!
<tag>Hello!
的内容。考虑以下情况:<.*?>Hello!
<
,然后快速找到一个闭合的>
,但没有找到>Hello!
。因此,.*?
继续寻找后面跟着Hello!
的>
。如果没有找到,它将一直查找到文档末尾才会放弃。然后,正则表达式引擎恢复扫描,直到找到另一个<
,然后再次尝试。我们已经知道结果如何,但是正则表达式引擎通常不知道;它会在文档中的每个<
上进行相同的操作。现在考虑其他的正则表达式:<[^>]*>Hello!
<
到>
,但无法匹配Hello!
。它将回溯到<
,然后退出并开始扫描另一个<
。它仍然像第一个正则表达式一样检查每个<
,但不会每次找到一个就搜索整个文档的结尾。.*?
实际上等效于负向先行断言。它的意思是“在消耗下一个字符之前,请确保正则表达式的剩余部分无法在此位置匹配。”换句话说,/<.*?>Hello!/
...等同于:
/<(?:(?!>Hello!).)*(?:>Hello!|\z(*FAIL))/
.
会继续消耗一个字符。)(*FAIL)
是Perl的回溯控制动词之一(也支持PHP)。|\z(*FAIL)
的意思是“或者到达文档结尾并放弃”)。/<[^>]*+>Hello!/
...或将其包装在原子组中:
/(?><[^>]*>)Hello!/
<.*?>Hello!
与<[^>]*>Hello!
进行比较并不太公平。在这种情况下,您的结束定界符实际上是>Hello!
,而[^>]
根本无法处理它。我试图在我的答案的最后一点中提到了这一点。 - KobiHello!
附加到原始正则表达式上,有效地将闭合分隔符从单个字符更改为多个字符序列。这会使.*?
版本变成潜在的黑洞,而[^>]*
版本仍然可以正常工作。我想说的是,在孤立状态下,两种风格之间实际上几乎没有什么可选择的;不过,如果正则表达式变得更加复杂,选择就变得至关重要了。 - Alan Moore
[^>]*
只有在其后面跟随否定类中的内容([^>]*>
在这种情况下)时才会不回溯(backtrack)。Kobi,我知道你知道并且可能意味着这一点,但我想确保其他人不认为[^>]*
和[^>]*+
(占有形式)是相同的。除此之外,答案很好! - Bart Kiers