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

测试字符串的同一部分是否满足多个要求

环视上一主题中进行了详细介绍,它是一个非常强大的概念。遗憾的是,初学者经常低估它,因为环视有点令人困惑。令人困惑的部分是环视是零长度的。因此,如果你有一个 regex,其中前瞻后跟另一段 regex,或者后顾前接另一段 regex,那么 regex 将遍历字符串的一部分两次。

一个更实际的示例可以说明这一点。假设我们想找到一个六个字母长且包含三个连续字母 cat的单词。实际上,我们可以在不环视的情况下匹配它。我们只需指定所有选项,并使用交替将它们组合在一起:cat\w{3}|\wcat\w{2}|\w{2}cat\w|\w{3}cat。足够简单。但是,如果你想找到一个长度在 6 到 12 个字母之间的单词,并且包含“cat”、“dog”或“mouse”,那么这种方法就会变得很笨拙。

环视来救援

在此示例中,我们基本上对成功匹配有两个要求。首先,我们想要一个长度为 6 个字母的单词。其次,我们找到的单词必须包含单词“cat”。

使用 \b\w{6}\b 匹配一个 6 个字母的单词很容易。匹配一个包含“cat”的单词同样容易:\b\w*cat\w*\b

将两者结合起来,我们得到:(?=\b\w{6}\b)\b\w*cat\w*\b。很容易!以下是它的工作原理。在字符串中尝试正则表达式的每个字符位置,引擎首先尝试正则表达式中的正向肯定先行断言。此子正则表达式,因此正向肯定先行断言,仅在字符串中当前字符位置位于字符串中的 6 个字母单词开头时才匹配。如果不是,则正向肯定先行断言失败,引擎继续尝试从字符串中下一个字符位置开始的正则表达式。

正向肯定先行断言的长度为零。因此,当正向肯定先行断言中的正则表达式找到 6 个字母的单词时,字符串中的当前位置仍位于 6 个字母单词的开头。正则表达式引擎在此位置尝试其余的正则表达式。因为我们已经知道可以在当前位置匹配 6 个字母的单词,所以我们知道 \b 匹配,并且第一个 \w* 匹配 6 次。然后,引擎回溯,减少 \w* 匹配的字符数,直到可以匹配 cat。如果无法匹配 cat,则引擎别无选择,只能从正则表达式的开头重新开始,从字符串中的下一个字符位置开始。这在刚刚找到的 6 个字母单词的第二个字母处,正向肯定先行断言将失败,导致引擎逐个字符前进,直到下一个 6 个字母单词。

如果可以成功匹配 cat,则第二个 \w* 消耗 6 个字母单词中剩余的字母(如果有)。之后,正则表达式中的最后一个 \b 保证匹配正向肯定先行断言中的第二个 \b 匹配的位置。我们的双重要求正则表达式已成功匹配。

优化我们的解决方案

尽管上述正则表达式运行良好,但它并不是最优的解决方案。如果您只是在文本编辑器中进行搜索,这并不是问题。但是,如果您要重复使用此正则表达式和/或在您正在开发的应用程序中对大量数据进行操作,那么优化操作是一个好主意。

如果您仔细检查正则表达式并按照正则表达式引擎应用它的方式进行操作,您可以自己发现这些优化,就像我们上面所做的那样。第三个也是最后一个 \b 保证匹配。由于 词边界 为零长度,因此不会更改正则表达式引擎返回的结果,我们可以删除它们,留下: (?=\b\w{6}\b)\w*cat\w*。尽管最后一个 \w* 也保证匹配,但我们不能删除它,因为它会向正则表达式匹配中添加字符。请记住,先行断言会丢弃其匹配,因此它不会影响正则表达式引擎返回的匹配。如果我们省略 \w*,则结果匹配将是包含“cat”的 6 个字母单词的开头,直到“cat”为止,而不是整个单词。

但我们可以优化第一个 \w*。按原样,它将匹配 6 个字母,然后回溯。但我们知道,在成功的匹配中,“cat”之前最多只有 3 个字母。因此,我们可以将其优化为 \w{0,3}。请注意,使星号变为惰性并不能充分优化此操作。惰性星号会更快地找到成功的匹配,但如果一个 6 个字母的单词不包含“cat”,它仍然会导致正则表达式引擎尝试在最后两个字母、最后一个字母,甚至在 6 个字母单词之外的一个字符处匹配“cat”。

因此,我们有 (?=\b\w{6}\b)\w{0,3}cat\w*。最后一个次要优化涉及第一个 \b。由于其本身长度为零,因此无需将其放在前瞻中。因此,最终的正则表达式为:\b(?=\w{6}\b)\w{0,3}cat\w*

你也可以用 \w{0,3} 替换最后的 \w*。但这不会产生任何差异。前瞻已经检查了我们是否处于一个 6 个字母的单词中,并且 \w{0,3}cat 已经匹配了该单词的 3 到 6 个字母。我们用 \w* 还是 \w{0,3} 结束正则表达式并不重要,因为无论哪种方式,我们都将匹配所有剩余的单词字符。由于匹配结果和找到匹配结果的速度相同,因此我们不妨使用更易于键入的版本。

更复杂的问题

那么,你将如何找到任何包含“cat”、“dog”或“mouse”的长度在 6 到 12 个字母之间的单词?同样,我们有两个要求,我们可以轻松地使用前瞻将它们组合起来:\b(?=\w{6,12}\b)\w{0,9}(cat|dog|mouse)\w*。一旦你掌握了诀窍,这非常容易。此正则表达式还会将“cat”、“dog”或“mouse”放入第一个反向引用中。