快速入门
教程
工具和语言
示例
参考
书评
正则表达式教程
简介
目录
特殊字符
不可打印字符
正则表达式引擎内部
字符类
字符类减法
字符类交集
简写字符类
锚点
单词边界
交替
可选项
重复
分组和捕获
反向引用
反向引用,第 2 部分
命名组
相对反向引用
分支重置组
自由间距和注释
Unicode
模式修饰符
原子分组
独占量词
前瞻和后顾
环视,第 2 部分
将文本排除在匹配之外
条件
平衡组
递归
子例程
无限递归
递归和量词
递归和捕获
递归和反向引用
递归和回溯
POSIX 方括号表达式
零长度匹配
继续匹配
本网站的更多内容
简介
正则表达式快速入门
正则表达式教程
替换字符串教程
应用程序和语言
正则表达式示例
正则表达式参考
替换字符串参考
书评
可打印 PDF
关于本网站
RSS Feed 和博客
RegexBuddy—Better than a regular expression tutorial!

独占量词

关于重复运算符或量词的主题解释了贪婪和懒惰重复之间的区别。贪婪和懒惰决定了正则表达式引擎尝试正则表达式模式的可能排列的顺序。贪婪量词首先尝试尽可能多地重复标记,并随着引擎回溯以找到整体匹配而逐渐放弃匹配。懒惰量词首先尽可能少地重复标记,并随着引擎通过正则表达式回溯以找到整体匹配而逐渐扩展匹配。

由于贪婪和懒惰改变了尝试排列的顺序,因此它们可以改变整体正则表达式匹配。但是,它们不会改变正则表达式引擎将回溯以尝试所有可能的正则表达式排列这一事实,以防找不到匹配项。

独占量词是一种防止正则表达式引擎尝试所有排列的方法。这主要出于性能原因。你还可以使用独占量词来消除某些匹配项。

在本教程中讨论的正则表达式风格中,JGsoftJavaPCRE支持独占量词。其中包括基于 PCRE 的正则表达式支持的语言,如PHPDelphiRPython从 Python 3.11 开始支持独占量词,Perl从 Perl 5.10 开始支持,Ruby从 Ruby 1.9 开始支持,Boost从 Boost 1.42 开始支持。

所有格量词的工作原理

与贪婪量词类似,所有格量词会尽可能多次重复标记。与贪婪量词不同,它在引擎回溯时不会放弃匹配。对于所有格量词,结果要么全有要么全无。您可以在量词后放置一个额外的 + 来使其成为所有格。 * 是贪婪的,*? 是惰性的,*+ 是所有格的。 ++?+{n,m}+ 也是所有格的。

让我们看看如果我们尝试将 "[^"]*+""abc" 匹配会发生什么。 " 匹配 "[^"] 匹配 abc,因为 星号 会重复它。最后的 " 然后匹配最后的 ",我们找到了一个整体匹配。在这种情况下,无论我们使用贪婪量词还是所有格量词,最终结果都是相同的。不过,性能会略有提升,因为所有格量词不必记住任何回溯位置。

在正则表达式失败的情况下,性能提升可能是显著的。如果主题是 "abc(没有结束引号),则上述匹配过程会以相同的方式发生,只是第二个 " 失败。当使用所有格量词时,没有步骤可以回溯到。正则表达式没有任何交替或非所有格量词,可以放弃其匹配的一部分来尝试正则表达式的不同排列。因此,当第二个 " 失败时,匹配尝试会立即失败。

如果我们使用 "[^"]*" 和贪婪量词,引擎将回溯。在 " 在字符串结尾失败后,[^"]* 将放弃一个匹配,留下 ab。然后 " 将无法匹配 c[^"]* 回溯到 a" 无法匹配 b。最后,[^"]* 回溯到匹配零个字符," 无法匹配 a。只有此时,所有回溯位置都已用尽,引擎才放弃匹配尝试。从本质上讲,此正则表达式执行的无用步骤与未匹配的开引号后面的字符一样多。

占有量词重要时

占有量词的主要实际好处是加速正则表达式。特别是,占有量词允许你的正则表达式更快地失败。在上面的示例中,当闭合引号无法匹配时,我们知道正则表达式不可能跳过引号。因此,无需回溯并检查引号。我们通过使量词占有来使正则表达式引擎意识到这一点。事实上,包括 JGsoft 引擎在内的一些引擎在编译正则表达式时会检测到 [^"]*" 是互斥的,并自动使星号占有。

现在,像具有单个量词的正则表达式那样的线性回溯非常快。你不太可能注意到速度差异。但是,当你嵌套量词时,占有量词可能会挽救你。嵌套量词意味着你在组内有一个或多个重复标记,并且组也会重复。这时,灾难性回溯 经常会抬其丑陋的头颅。在这种情况下,你将依赖占有量词和/或原子分组来挽救局面。

占有量词可以更改匹配结果

使用所有格量词可以更改匹配尝试的结果。由于没有进行回溯,并且需要贪婪量词回溯的匹配不会通过所有格量词找到。例如,".*""abc"x 中匹配 "abc",但 ".*+" 根本不匹配此字符串。

在这两个正则表达式中,第一个 " 匹配字符串中的第一个 "。然后,重复的点匹配字符串 abc"x 的剩余部分。然后,第二个 " 无法在字符串末尾匹配。

现在,这两个正则表达式的路径发生了分歧。所有格点星想要全部。不进行回溯。由于 " 失败,因此没有排列可以尝试,并且整体匹配尝试失败。贪婪的点星虽然最初抓住了所有东西,但愿意放弃。它将一次回溯一个字符。回溯到 abc"" 无法匹配 x。回溯到 abc" 匹配 "。找到整体匹配 "abc"

从本质上讲,这里的教训是,在使用所有格量词时,你需要确保将所有格量词应用于的内容不应该能够匹配其后跟的内容。上述示例中的问题是,点也匹配闭合引号。这阻止我们使用所有格量词。前一节中的否定字符类无法匹配闭合引号,因此我们可以使其成为所有格。

使用原子分组代替所有格量词

从技术上讲,所有格量词是一种符号方便,用于在单个量词周围放置原子组。所有支持所有格量词的正则表达式风格也支持原子分组。但并非所有支持原子分组的正则表达式风格都支持所有格量词。使用这些风格,你可以使用原子组实现完全相同的结果。

基本上,不要写 X*+,而要写 (?>X*)。需要注意的是,量化的标记 X 和量词都位于原子组内。即使 X 是一个组,你仍然需要在其周围放置一个额外的原子组才能达到相同的效果。 (?:a|b)*+ 等效于 (?>(?:a|b)*),但不等效于 (?>a|b)*。后者是一个有效的正则表达式,但当用作更大正则表达式的一部分时,它不会产生相同的效果。

为了说明这一点,(?:a|b)*+b(?>(?:a|b)*)b 都无法匹配 ba|b 匹配 b。星号得到满足,并且它占有或原子组的事实将导致星号忘记其所有回溯位置。正则表达式中的第二个 b 没有剩余的匹配项,并且整体匹配尝试失败。

在正则表达式 (?>a|b)*b 中,原子组强制交替放弃其回溯位置。这意味着如果匹配了 a,如果正则表达式的其余部分失败,它不会返回来尝试 b。由于星号位于组的外部,因此它是一个正常的贪婪星号。当第二个 b 失败时,贪婪星号回溯到零次迭代。然后,第二个 b 匹配主题字符串中的 b

在将某人使用占有量词编写的正则表达式转换为没有占有量词的正则表达式风格时,这种区别尤为重要。当然,你可以让 RegexBuddy 等工具为你进行转换。