翻译说明
greedy
: 贪婪型, 最大匹配方式;
reluctant
: 懒惰型, 最小匹配方式;
possessive
: 独占型, 全部匹配方式; 也翻译为[支配型
];这3种量词, 是修饰量词的量词, 可以理解为正则格式重复的匹配类型。
量词
量词(Quantifier)用来指定某部分正则所重复的次数。为了方便,本文分别介绍 Pattern API 规范中的3种类型, 分别是 greedy(贪婪), reluctant(懒惰), 和 possessive(独占) 量词。表面上看, X?
, X??
和 X?+
这几种量词都差不多, 都是匹配 “出现0到1次大写的X”。 下文将会讲解他们在实现上的细微差别。
Greedy(贪婪) | Reluctant(懒惰) | Possessive(独占) | 说明 |
---|---|---|---|
X? |
X?? |
X?+ |
X , 出现0或1次 |
X* |
X*? |
X*+ |
X , 出现0到多次 |
X+ |
X+? |
X++ |
X , 出现1到多次 |
X{n} |
X{n}? |
X{n}+ |
X , 精确匹配 n 次 |
X{n,} |
X{n,}? |
X{n,}+ |
X , 最少出现 n 次 |
X{n,m} |
X{n,m}? |
X{n,m}+ |
X , 最少出现 n 次, 最多出现 m 次 |
我们先创建3个基本的正则表达式:字母 “a
” 后面紧跟 ?
, *
, 或者 +
。然后使用贪婪型来进行匹配。 先来看看碰到空字符串 ""
是什么情况:
Enter your regex: a?
Enter input string to search:
I found the text "" starting at index 0 and ending at index 0.
Enter your regex: a*
Enter input string to search:
I found the text "" starting at index 0 and ending at index 0.
Enter your regex: a+
Enter input string to search:
No match found.
零长匹配
上面的示例中, 前两个正则成功匹配, 因为 a?
和 a*
都允许出现 0 次 a
. 且开始索引和结束索引 都是 0, 这和之前所见的情形略有不同。空字符串""
的长度为0, 所以只能在索引0处匹配。这种情况称为零长匹配(Zero-Length Match).
零长匹配可能出现的情况包括: 空文本, 字符串起始处, 字符串结尾处, 以及任意两个字符之间. 零长匹配很容易辨认, 因为开始索引和结束索引的位置相等。
下面来看几个零长匹配的示例。输入文本为单个字母 “a
” , 你会看到一些有趣的地方:
Enter your regex: a?
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
Enter your regex: a*
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
Enter your regex: a+
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
3种量词都可以匹配到字母”a”, 但前两个还找到了一次零长匹配, 在 index=1 的位置, 也就是字符串结尾之处. 可以看到, 匹配器先在 index=0 和 index=1 之间找到了字符 “a”, 往后类推, 直到再也匹配不到为止. 根据使用量词的不同, 文本结尾处的空白(nothing)可能被匹配到, 也可能不被匹配到。
我们看看连续输入5个字母”a
“的情况:
Enter your regex: a?
Enter input string to search: aaaaa
I found the text "a" starting at index 0 and ending at index 1.
I found the text "a" starting at index 1 and ending at index 2.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "a" starting at index 3 and ending at index 4.
I found the text "a" starting at index 4 and ending at index 5.
I found the text "" starting at index 5 and ending at index 5.
Enter your regex: a*
Enter input string to search: aaaaa
I found the text "aaaaa" starting at index 0 and ending at index 5.
I found the text "" starting at index 5 and ending at index 5.
Enter your regex: a+
Enter input string to search: aaaaa
I found the text "aaaaa" starting at index 0 and ending at index 5.
正则 a?
对每个字母进行1次匹配, 因为它匹配的是0到1个 "a"
. 正则 a*
会匹配2次: 其中第1次匹配多个连续的字母 “a” , 第2次是零长匹配, 字符串结束位置 index=5 的地方. 而 a+
只会匹配所有出现的”a”字母, 忽略最后的空白(nothing)。
现在, 我们想知道, 前2个正则在碰到其他字母时会发生什么. 例如碰到 “ababaaaab” 之中的 b
字母时。
请看示例:
Enter your regex: a?
Enter input string to search: ababaaaab
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "" starting at index 3 and ending at index 3.
I found the text "a" starting at index 4 and ending at index 5.
I found the text "a" starting at index 5 and ending at index 6.
I found the text "a" starting at index 6 and ending at index 7.
I found the text "a" starting at index 7 and ending at index 8.
I found the text "" starting at index 8 and ending at index 8.
I found the text "" starting at index 9 and ending at index 9.
Enter your regex: a*
Enter input string to search: ababaaaab
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "" starting at index 3 and ending at index 3.
I found the text "aaaa" starting at index 4 and ending at index 8.
I found the text "" starting at index 8 and ending at index 8.
I found the text "" starting at index 9 and ending at index 9.
Enter your regex: a+
Enter input string to search: ababaaaab
I found the text "a" starting at index 0 and ending at index 1.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "aaaa" starting at index 4 and ending at index 8.
字母 “b” 出现在索引为 1, 3, 8 的位置, 输出结果也表明零长匹配出现在这些地方. 正则 a?
不会专门查找字母”b”, 而只查找 存在/或不存在字母 “a” 的地方. 如果量词允许0次匹配, 则只要不是 “a” 字母的地方都会出现一次零长匹配. 其余的”a”则根据前面介绍的规则进行匹配。
要精确匹配某个格式 n 次, 只需要在大括号内指定数字即可:
Enter your regex: a{3}
Enter input string to search: aa
No match found.
Enter your regex: a{3}
Enter input string to search: aaa
I found the text "aaa" starting at index 0 and ending at index 3.
Enter your regex: a{3}
Enter input string to search: aaaa
I found the text "aaa" starting at index 0 and ending at index 3.
正则 a{3}
匹配连续出现的三个“a
”字母。第一次测试匹配失败, 是因为字母a
的数量不足. 第二次测试时, 字符串中刚好包含3个 a
字母, 所以匹配了一次。第三次测试也触发了一次匹配, 因为输入文本的签名有3个 a
字母. 后面再出现的字母, 与第一次匹配无关。如果后面还有这种格式的字符串, 则使用后面的子串触发后续匹配:
Enter your regex: a{3}
Enter input string to search: aaaaaaaaa
I found the text "aaa" starting at index 0 and ending at index 3.
I found the text "aaa" starting at index 3 and ending at index 6.
I found the text "aaa" starting at index 6 and ending at index 9.
要求某种格式至少出现n
次,可以在数字后面加一个逗号,例如:
Enter your regex: a{3,}
Enter input string to search: aaaaaaaaa
I found the text "aaaaaaaaa" starting at index 0 and ending at index 9.
同样是9个字母a, 这里就只匹配了一次,因为9个 a
字母的序列也满足 “最少3个a字母” 的需求。
如果要指定出现次数的最大值,在大括号内加上第二个数字即可:
Enter your regex: a{3,6} // 最少3个,最多6个a字母
Enter input string to search: aaaaaaaaa
I found the text "aaaaaa" starting at index 0 and ending at index 6.
I found the text "aaa" starting at index 6 and ending at index 9.
这里的第一个匹配在达到上限的6个字符时停止. 第二个匹配包含了剩下的字母, 恰好是要求的最小字符个数: 三个 a
. 如果输入的文本再少一个字符, 第二次匹配就不会发生, 因为只有2个 a
则匹配不了该格式。
关联到捕获组和/或字符集的量词
到目前为止, 我们只是用量词来测试了单个字符的情况. 但实际上, 量词只关联到一个字符上, 所以正则 “abc+
” 的含义是: “字母a
, 后面跟着字母b
, 然后再跟着1到多个字母c
”. 而不表示1到多次的 “abc”. 当然, 量词可以关联到字符组(Character Class)和捕获组(Capturing Group), 例如 [abc]+
, 表示 “出现1到多次的a或b或c, 也就是abc三个字母组成的任意组合”), 而正则 (abc)+
表示 “abc
” 这个 group 整体出现 1次到多次, 例如 abcabcabc
。
让我们看一个具体的示例, 指定分组 dog
连续出现三次。
Enter your regex: (dog){3}
Enter input string to search: dogdogdogdogdogdog
I found the text "dogdogdog" starting at index 0 and ending at index 9.
I found the text "dogdogdog" starting at index 9 and ending at index 18.
Enter your regex: dog{3}
Enter input string to search: dogdogdogdogdogdog
No match found.
第一个示例, 匹配了3次, 因为量词作用于整个捕获组. 如果把小括号去掉, 就会匹配失败, 因为这时候量词{3}
只作用于字母”g
“。
类似地,我们将量词作用于整个字符组(character class):
Enter your regex: [abc]{3}
Enter input string to search: abccabaaaccbbbc
I found the text "abc" starting at index 0 and ending at index 3.
I found the text "cab" starting at index 3 and ending at index 6.
I found the text "aaa" starting at index 6 and ending at index 9.
I found the text "ccb" starting at index 9 and ending at index 12.
I found the text "bbc" starting at index 12 and ending at index 15.
Enter your regex: abc{3}
Enter input string to search: abccabaaaccbbbc
No match found.
第一个示例中, 量词 {3}
作用于整个字符组, 在第二个示例中, 量词只作用于字母 “c”。
贪婪,懒惰和全量量词之间的区别
贪婪(Greedy),懒惰(Reluctant)和全量(Possessive)这三种量词模式之间有一些细微的差别。
贪婪量词(Greedy quantifier), 其试图在第一次匹配时就吃掉所有的输入字符. 如果尝试吃掉整个字符串失败, 则放过最后一个字符, 并再次尝试匹配, 重复这个过程, 直到找到一个匹配, 或者是没有可回退的字符为止. 根据正则中的量词, 最后尝试匹配的可能是0或1个字符。
懒惰量词(reluctant quantifier),采取的策略正好相反: 从输入字符串的起始处, 每吃下一个字符,就尝试进行一次匹配. 最后才会尝试匹配整个输入字符串。
独占量词(possessive quantifier), 则是吃下整个输入字符串, 只进行一次匹配尝试. 独占量词从不后退, 即使匹配失败, 这点是和贪婪量词的不同。
请看下面的示例:
Enter your regex: .*foo // Java默认贪婪型
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.
Enter your regex: .*?foo // 懒惰型
Enter input string to search: xfooxxxxxxfoo
I found the text "xfoo" starting at index 0 and ending at index 4.
I found the text "xxxxxxfoo" starting at index 4 and ending at index 13.
Enter your regex: .*+foo // 独占模式
Enter input string to search: xfooxxxxxxfoo
No match found.
第一个示例使用的是贪婪量词 .*
, 匹配0到多个的任意字符(anything), 紧随其后的是字母 “f” “o” “o”。因为是贪婪量词, .*
部分首先吃掉整个输入字符串, 发现整个表达式匹配不成功, 因为最后三个字母(“f” “o” “o”)已经被 .*
吃掉了; 然后, 匹配器放开最后1个字符,再放开最后1个字符,再放开最后1个字符, 直到右边剩下 “foo” 为止, 这时候匹配成功, 查找结束。
第二个示例是懒惰型, 所以最开始什么都不吃. 因为后面不是 “foo”,所以不得不吃下第一个字母(“x”), 然后就触发了第一次匹配, 在索引0到4之间。接着从索引4的后面再次进行匹配尝试, 直到尝试完整个输入字符串。在索引4到13之间触发了第二次匹配。
第三个例子, 使用的是独占量词, 所以没有匹配成功。在这个示例中, 因为整个输入字符串都被 .*+
吃掉了, 剩下的空白自然不能对应 “foo”. 由此可知, 独占量词只能用于匹配所有字符的情况, 它从不后退; 如果都不能匹配到, 独占量词的性能会比贪婪型好一些。
原文链接: https://docs.oracle.com/javase/tutorial/essential/regex/quant.html
相关链接:
翻译日期: 2018年1月2日