【问题标题】:Matching text between delimiters: greedy or lazy regular expression?分隔符之间的匹配文本:贪婪或惰性正则表达式?
【发布时间】:2023-04-10 09:56:01
【问题描述】:

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

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

有什么特别的理由偏爱其中一个吗?

【问题讨论】:

    标签: regex language-agnostic greedy regex-greedy


    【解决方案1】:

    一些优点:

    [^>]*:

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

    .*?

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

    【讨论】:

    • 请注意,[^>]* 只会不会在其后跟被否定类中的内容(本例中为[^>]*>)后回溯。 Kobi,我知道你知道并且可能是这个意思,但想确保其他人不认为[^>]*[^>]*+(占有)是相同的。除此之外,很好的答案!
    【解决方案2】:

    第一个更明确,i。 e.它绝对将结束分隔符排除在匹配文本的一部分之外。这在第二种情况下无法保证(如果正则表达式被扩展为匹配的不仅仅是这个标签)。

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

    <tag1><tag2>Hello!
    

    &lt;[^&gt;]*&gt;Hello! 将匹配

    <tag2>Hello!
    

    【讨论】:

    • 很好的例子,在某些情况下不情愿的匹配可以匹配两个子字符串,而许多人希望它只匹配一个。
    • +1,很好的例子。这次真的很难选择答案,但我选择了 Kobis,因为他列出了两种选择的优缺点。
    【解决方案3】:

    大多数人在处理此类问题时没有考虑到,当正则表达式无法找到匹配项时会发生什么。 那是最有可能出现杀手级性能漏洞的时候。例如,以 Tim 为例,您正在寻找类似 @​​987654322@ 的内容。考虑会发生什么:

    <.*?>Hello!
    

    正则表达式引擎找到&lt;,并很快找到关闭&gt;,但不是&gt;Hello!。所以.*? 继续寻找一个&gt; 后跟Hello!。如果没有,它将在放弃之前一直走到文档的末尾。然后正则表达式引擎继续扫描,直到找到另一个&lt;,然后再试一次。 我们已经知道结果会怎样,但是正则表达式引擎通常不知道;它与文档中的每个&lt; 都经过相同的繁琐。现在考虑另一个正则表达式:

    <[^>]*>Hello!
    

    和以前一样,它从&lt; 快速匹配到&gt;,但无法匹配Hello!。它将回溯到&lt;,然后退出并开始扫描另一个&lt;。它仍然会像第一个正则表达式一样检查每个&lt;,但它不会在每次找到一个时一直搜索到文档的末尾。

    但情况比这更糟。如果您考虑一下,.*? 实际上相当于负前瞻。它的意思是“在使用下一个字符之前,确保正则表达式的其余部分不能在这个位置匹配。”换句话说,

    /<.*?>Hello!/
    

    ...等价于:

    /<(?:(?!>Hello!).)*(?:>Hello!|\z(*FAIL))/
    

    因此,在您执行的每个位置上,不仅是正常的匹配尝试,而且是更昂贵的前瞻。 (它的成本至少是前者的两倍,因为先行必须扫描至少一个字符,然后 . 继续并消耗一个字符。)

    (*FAIL) 是 Perl 的backtracking-control verbs 之一(PHP 也支持)。|\z(*FAIL) 的意思是“或到达文档末尾并放弃”。)

    最后,否定字符类方法还有另一个优点。虽然它不像(正如@Bart 指出的那样)表现得像量词是所有格,但如果你的风格支持它,没有什么可以阻止你使它具有所有格:

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

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

    /(?><[^>]*>)Hello!/
    

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

    【讨论】:

    • 好答案。然而,这里相当重要的一点是&lt;.*?&gt;Hello!&lt;[^&gt;]*&gt;Hello! 的比较并不公平。在这种情况下,您的结束分隔符实际上是&gt;Hello!,而不是&gt;,而[^&gt;] 根本无法处理。我试图在我回答的最后一点中提到这一点。
    • 是的,将Hello! 附加到原始正则表达式可以有效地将结束分隔符从单个字符更改为多字符序列。这会将.*? 版本变成一个潜在的黑洞,而[^&gt;]* 版本仍然可以正常工作。我是说,孤立地看,这两种风格几乎没有什么可以选择的。不过,让正则表达式稍微复杂一点,选择就变得至关重要。
    猜你喜欢
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 2012-11-30
    • 2016-12-14
    • 1970-01-01
    • 2011-04-11
    • 2011-08-29
    • 2012-01-05
    相关资源
    最近更新 更多