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

星号和加号重复

已经介绍了一个重复运算符或量词:问号。它告诉引擎尝试匹配前一个标记零次或一次,实际上使其成为可选的。

星号或乘号告诉引擎尝试匹配前一个标记零次或多次。加号告诉引擎尝试匹配前一个标记一次或多次。 <[A-Za-z][A-Za-z0-9]*> 匹配没有任何属性的 HTML 标记。尖括号是 文字。第一个 字符类 匹配一个字母。第二个字符类匹配一个字母或数字。星号重复第二个字符类。由于我们使用了星号,因此即使第二个字符类没有匹配任何内容也没有关系。因此,我们的正则表达式将匹配 <B> 这样的标记。在匹配 <HTML> 时,第一个字符类将匹配 H。星号将导致第二个字符类重复三次,分别匹配 TML

我也可以使用 <[A-Za-z0-9]+>。我没有使用,因为此正则表达式将匹配 <1>,这不是一个有效的 HTML 标记。但是,如果您知道要搜索的字符串不包含任何此类无效标记,则此正则表达式可能就足够了。

限制重复

还有一个量词,允许您指定一个标记可以重复多少次。语法是 {min,max},其中 min 是零或一个正整数,表示最小匹配次数,而 max 是一个等于或大于 min 的整数,表示最大匹配次数。如果存在逗号但省略了 max,则最大匹配次数为无穷大。因此,{0,1}? 相同,{0,}* 相同,{1,}+ 相同。省略逗号和 max 告诉引擎重复标记恰好 min 次。

您可以使用 \b[1-9][0-9]{3}\b 匹配 1000 到 9999 之间的一个数字。 \b[1-9][0-9]{2,4}\b 匹配 100 到 99999 之间的一个数字。请注意使用了 词边界

小心贪婪!

假设你想使用正则表达式匹配一个 HTML 标签。你知道输入将是一个有效的 HTML 文件,所以正则表达式不需要排除任何尖括号的无效用法。如果它位于尖括号之间,它就是一个 HTML 标签。

大多数正则表达式新手会尝试使用 <.+>。当他们在像 This is a <EM>first</EM> test 这样的字符串上测试它时,他们会感到惊讶。你可能希望正则表达式匹配 <EM>,然后在该匹配之后继续匹配 </EM>

但它没有。正则表达式将匹配 <EM>first</EM>。显然不是我们想要的。原因是加号是贪婪的。也就是说,加号会导致正则表达式引擎尽可能多地重复前一个标记。只有当这导致整个正则表达式失败时,正则表达式引擎才会回溯。也就是说,它将返回加号,让它放弃最后一次迭代,并继续执行正则表达式的其余部分。让我们深入了解正则表达式引擎,详细了解它是如何工作的,以及为什么这会导致我们的正则表达式失败。在那之后,我将向你展示两个可能的解决方案。

与加号一样,星号和使用大括号的重复也是贪婪的。

深入了解正则表达式引擎

正则表达式中的第一个标记是 <。这是一个文字。正如我们已经知道的,它将匹配的第一个位置是字符串中的第一个 <。下一个标记是点,它匹配除换行符之外的任何字符。点被加号重复。加号是贪婪的。因此,引擎将尽可能多地重复点。点匹配 E,所以正则表达式继续尝试用点匹配下一个字符。M 被匹配,点再次被重复一次。下一个字符是 >。你现在应该看到问题了。点匹配 >,引擎继续重复点。点将匹配字符串中所有剩余的字符。当引擎到达字符串末尾后的空隙时,点失败。只有在这一点上,正则表达式引擎才会继续执行下一个标记:>

到目前为止,<.+ 已经匹配了 <EM>first</EM> test,引擎已经到达字符串的末尾。> 在这里无法匹配。引擎记住加号重复点的次数比要求的次数多。(记住,加号要求点只匹配一次。)引擎不会承认失败,而是会回溯。它将减少加号的重复次数,然后继续尝试正则表达式的其余部分。

所以 .+ 的匹配被减少到 EM>first</EM> tes。正则表达式中的下一个标记仍然是 >。但现在字符串中的下一个字符是最后一个 t。同样,它们无法匹配,导致引擎进一步回溯。到目前为止的总匹配减少到 <EM>first</EM> te。但 > 仍然无法匹配。因此,引擎继续回溯,直到 .+ 的匹配减少到 EM>first</EM。现在,> 可以匹配字符串中的下一个字符。正则表达式中的最后一个标记已经匹配。引擎报告 <EM>first</EM> 已成功匹配。

请记住,正则表达式引擎热衷于返回匹配项。它不会继续回溯以查看是否存在其他可能的匹配项。它将报告找到的第一个有效匹配项。由于贪婪,这是最左边的最长匹配项。

懒惰而不是贪婪

解决此问题的快速方法是使加号变为懒惰而不是贪婪。懒惰量词有时也称为“非贪婪”或“勉强”。您可以在正则表达式中加号后加上问号来实现。您还可以对星号、花括号和问号本身执行相同的操作。因此,我们的示例变为 <.+?>。让我们再次查看正则表达式引擎内部。

同样,< 匹配字符串中的第一个 <。下一个标记是点,这次由懒惰加号重复。这告诉正则表达式引擎尽可能少地重复点。最少为一个。因此,引擎用 E 匹配点。要求已得到满足,引擎继续使用 >M。这失败了。同样,引擎将回溯。但这一次,回溯将强制懒惰加号扩展而不是缩小其范围。因此,.+ 的匹配项扩展为 EM,引擎再次尝试继续使用 >。现在,> 已成功匹配。正则表达式中的最后一个标记已匹配。引擎报告 <EM> 已成功匹配。更像这样。

懒惰的替代方案

在这种情况下,有一个比使加号变为懒惰更好的选择。我们可以使用贪婪加号和否定字符类<[^>]+>。之所以这样做更好,是因为回溯。当使用懒惰加号时,引擎必须为它试图匹配的 HTML 标记中的每个字符回溯。当使用否定字符类时,当字符串包含有效的 HTML 代码时,根本不会发生回溯。回溯会减慢正则表达式引擎的速度。在文本编辑器中进行单次搜索时,您不会注意到差异。但是,当在您编写的脚本中的紧密循环中或在 EditPad Pro 的自定义语法着色方案中重复使用此类正则表达式时,您将节省大量的 CPU 周期。

只有 regex 指导引擎 回溯。文本指导引擎不回溯,因此不会受到速度损失。但它们也不支持惰性量词。

重复 \Q…\E 转义序列

\Q…\E 序列 转义一个字符字符串,将它们作为字面字符进行匹配。转义的字符被视为单个字符。如果在 \E 后放置量词,则它将只应用于最后一个字符。例如,如果将 \Q*\d+*\E+ 应用于 *\d+**\d+*,则匹配结果将是 *\d+**。只有星号被重复。Java 4 和 5 有一个 bug,导致整个 \Q…E 序列被重复,从而将整个主题字符串作为匹配结果。此问题已在 Java 6 中修复。