文档

Java™ 教程-Java Tutorials 中文版
量词
Trail: Essential Classes
Lesson: Regular Expressions

量词

Quantifiers (量词) 允许你指定要匹配的匹配项次数。为方便起见,下面介绍了描述贪婪,懒惰和占有量词的 Pattern API 规范的三个部分。乍一看,似乎量词 X?X??X?+ 完全相同,因为它们都承诺匹配“X,一次或根本不匹配”。有一些细微的实现差异,将在本节末尾附近解释。

贪婪 懒惰 占有 含义
X? X?? X?+ X,一次或零次
X* X*? X*+ X,零次或多次
X+ X+? X++ X,一次或多次
X{n} X{n}? X{n}+ X,恰好 n times
X{n,} X{n,}? X{n,}+ X,至少 n
X{n,m} X{n,m}? X{n,m}+ X,至少 n 但不超过 m

让我们通过创建三个不同的正则表达式来开始我们对贪婪量词的看法:字母“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* 都允许字母 a 的零次匹配项。你还会注意到开始和结束索引都是零,这与我们到目前为止看到的任何示例都不同。空输入字符串 "" 没有长度,因此测试只是匹配索引 0 处的任何内容。这种匹配被称为 zero-length matches (零长度匹配)。在以下几种情况下可能发生零长度匹配:在空输入字符串中,在输入字符串的开头,在输入字符串的最后一个字符之后,或在输入字符串的任意两个字符之间。零长度匹配很容易识别,因为它们始终在相同的索引位置开始和结束。

让我们通过几个例子来探索零长度匹配。将输入字符串更改为单个字母“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.

所有三个量词都找到了字母“a”,但前两个也在索引 1 处找到了零长度匹配;也就是说,在输入字符串的最后一个字符之后。请记住,匹配器将字符“a”视为位于索引 0 和索引 1 之间的单元格中,并且我们的测试工具循环直到它无法再找到匹配项。根据所使用的量词,最后一个字符后索引处“无”的存在可能触发也可能不触发匹配。

现在连续五次将输入字符串更改为字母“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? 找到每个字符的单独匹配,因为它匹配“a”出现零或一次。表达式 a* 找到两个单独的匹配:第一个匹配中的所有字母“a”,然后是索引 5 处的最后一个字符后的零长度匹配。最后,a+ 匹配所有字母“a”组成的匹配项,忽略最后一个索引处“无”的存在。

此时,如果前两个量词遇到“a”以外的字母,你可能想知道结果会是什么。例如,如果遇到字母“b”会发生什么,如“ababaaaab”?

我们来看看:

 
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”的存在(或缺少)。如果量词允许匹配“a”零次,则输入字符串中不是“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”。第一个测试失败,因为输入字符串没有足够的匹配。第二个测试在输入字符串中包含 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 满足“至少” 3 个 a 的需要。

最后,要指定出现次数的上限,请在大括号内添加第二个数字:

 
Enter your regex: a{3,6} // find at least 3 (but no more than 6) a's in a row
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 个字符的上限。第二个匹配包括剩余的内容,恰好是 3 个 a — 满足此匹配允许的最小字符数。如果输入字符串短一个字符,则不会有第二个匹配,因为只剩下两个 a。

使用量词捕获组和字符类

到目前为止,我们只对包含一个字符的输入字符串测试了量词。实际上,量词只能一次附加到一个字符,因此正则表达式“abc+”表示“a,后跟 b,后跟 c 一次或多次”。它不意味着“abc”一次或多次。但是,量词也可以附加到 Character ClassesCapturing Groups,例如 [abc]+(a 或 b 或 c,一次或多次) 或 (abc)+(组“abc”,一次或多次)。

让我们通过指定每行三个的 (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} 现在仅适用于字母“g”。

同样,我们可以将量词应用于整个字符类:

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”。

贪婪,懒惰和占有量词之间的差异

贪婪,懒惰和占有量词之间存在细微差别。

贪婪量词被认为是“贪婪的”,因为它们在尝试第一次匹配之前,强制匹配器读入(或称为 eat)整个输入字符串。如果第一次匹配尝试(整个输入字符串)失败,匹配器将输入字符串后退一个字符并再次尝试,重复该过程直到找到匹配项或者没有剩余字符可以后退。根据表达式中使用的量词,它将尝试匹配的最后内容是 1 或 0 个字符。

然而,懒惰量词采用相反的方法:它们从输入字符串的开头开始,然后懒惰地一次吃一个字符来寻找匹配。他们尝试的最后内容是整个输入字符串。

最后,占有量词总是占用整个输入字符串,尝试一次(并且仅一次)匹配。与贪婪量词不同,占有量词永远不会后退,即使这样做也会使整体匹配成功。

为了说明,请考虑输入字符串 xfooxxxxxxfoo

 
Enter your regex: .*foo  // greedy quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.

Enter your regex: .*?foo  // reluctant quantifier
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 // possessive quantifier
Enter input string to search: xfooxxxxxxfoo
No match found.

第一个例子使用贪婪量词 .* 来找到“任何东西”,零次或多次,然后是字母 "f" "o" "o"。因为量词是贪婪,所以表达式的 .* 部分首先会占用整个输入字符串。此时,整体表达式不能成功,因为已经消耗了最后三个字母("f" "o" "o")。因此,匹配器一次缓慢地后退一个字母,直到最右边的“foo”重新出现,此时匹配成功并且搜索结束。

然而,第二个例子是懒惰的,所以它首先消耗“无”。因为“foo”没有出现在字符串的开头,所以它被强制吞下第一个字母(“x”),这会触发 0 和 4 的第一个匹配。我们的测试工具将继续此过程,直到输入字符串耗尽为止。它在 4 和 13 找到另一场匹配。

第三个例子找不到匹配,因为量词是占有。在这种情况下,整个输入字符串由 .*+ 消耗,不留下任何内容以满足表达式末尾的“foo”。使用占有量词来表示你想要抓住所有东西而不会后退的情况;在没有立即找到匹配的情况下,它将胜过等效的贪婪量词。


Previous page: Predefined Character Classes
Next page: Capturing Groups