<table((?!</table>).)*</table>
匹配我所有的表标签。然而
<table(.(?!</table>))*</table>
不。 如果我尝试用文字写出表达,第二个似乎是有意义的,但我无法理解第一个。
有什么区别?
作为参考,我从这里得到了术语"温和贪婪令牌": 回火贪婪令牌解决方案
什么是赘忍贪婪令牌?
rexegg.com 温和的贪婪令牌引用非常简洁:
在
(?:(?!{END}).)*
中,*
量词适用于一个点,但它现在是一个回火的点。负前瞻(?!{END})
断言当前位置后面的不是字符串{END}
。因此,点永远无法匹配{END}
的左大括号,保证我们不会跳过{END}
分隔符。
就是这样:一个温和的贪婪令牌是字符序列的一种否定字符类(参见单个字符的否定字符类)。
注意:回火贪婪标记和否定字符类之间的区别在于,前者与序列本身以外的文本并不真正匹配,而是不启动该序列的单个字符。 即 (?:(?!abc|xyz).)+
不会匹配defabc
中的def
,但会匹配def
和bc
,因为a
启动了禁止的abc
序列,而bc
没有。
它包括:
-
(?:...)*
- 一个量化的非捕获组(它可能是一个捕获组,但捕获每个单独的字符没有意义)(可以+
*
,这取决于是否预期空字符串匹配) -
(?!...)
- 一个负面的前瞻性,实际上对当前位置右侧的值施加了限制 -
.
- (或任何(通常是单个)字符)消费模式。
但是,我们总是可以通过在负前瞻中使用交替来进一步缓和令牌(例如 (?!{(?:END|START|MID)})
) 或将全匹配点替换为否定的字符类(例如 尝试仅匹配标签内的文本时(?:(?!START|END|MID)[^<>])
)。
消耗零件放置
请注意,没有提到在前瞻之前放置消耗部分(原始回火贪婪令牌中的点)的结构。Avinash的回答清楚地解释了这部分:(.(?!</table>))*
首先匹配任何字符(但没有DOTALL修饰符的换行符),然后检查它是否没有跟</table>
导致<table>table</table>
中无法匹配e
。*消耗部分(.
)必须放在回火前瞻之后。
我们什么时候应该使用脾气暴躁的贪婪令牌?
Rexegg.com 给出了一个想法:
- 当我们想要匹配分隔符 1 和分隔符 2 之间的文本块,中间没有子字符串 3 时(例如
{START}(?:(?!{(?:MID|RESTART)}).)*?{END}
- 当我们想要匹配包含特定模式的文本块而不溢出后续块时(例如,而不是像
<table>.*?chair.*?</table>
那样的懒惰点匹配,我们会使用<table>(?:(?!chair|</?table>).)*chair(?:(?!<table>).)*</table>
之类的东西)。 - 当我们想匹配 2 个字符串之间尽可能短的窗口时。当您需要从
abc 1 abc 2 xyz
获取abc 2 xyz
时,延迟匹配将无济于事(请参阅abc.*?xyz
和abc(?:(?!abc).)*?xyz
)。
性能问题
温和的贪婪令牌会消耗资源,因为在与消耗模式匹配的每个字符之后都会执行前瞻检查。展开循环技术可以显着提高温和的贪婪代币性能。
比如说,我们想在 abc 1 abc 2 xyz 3 xyz 中匹配abc 2 xyz
。我们可以跳过所有未a
或[^ax]*
x
的字符,abc(?:(?!abc|xyz).)*xyz
xyz
abc
然后匹配所有未跟bc
(带a(?!bc)
)的a
和所有不跟yz
(带x(?!yz)
)的x
: abc[^ax]*(?:a(?!bc)[^ax]*|x(?!yz)[^ax]*)*xyz
.
((?!</table>).)*
将检查要匹配的特定字符一定不是字符串</table>
中的起始字符。如果是,则它仅与该特定字符匹配。 *
重复相同的零次或多次。
(.(?!</table>))*
仅当字符后跟没有</table>
、零次或更多次时,才会匹配任何字符。因此,这将匹配表标记中除最后一个字符之外的所有字符,因为最后一个字符后跟 </table>
。以下模式</table>
断言在比赛结束时必须有一个结束表标记。这会使匹配失败。
看这里。
一个温和的贪婪令牌实际上只是意味着:
"匹配,但只能达到一定程度"
你怎么做:
您将不想匹配的令牌作为负面展望
(?!notAllowedToMatch)
在点.
前面(匹配任何一个事情),然后你用星*
重复整个事情:
((?!notAllowedToMatch).)*
工作原理:
"看,吃一个"一遍又一遍,在输入字符串中一次从左到右移动一个字符,直到看到不允许的序列(或字符串的结尾),此时匹配停止。
Wiktor更详细的回答很好,但我只是认为应该有一个更简单的解释。