文档

Java™ 教程
隐藏目录
量词
路径:基本Java类
课程:正则表达式

量词

量词X?X??X?+X
贪婪 勉强 占有性 含义
X? X?? X?+ X,出现一次或者不出现
X* X*? X*+ X,出现零次或多次
X+ X+? X++ X,出现一次或多次
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

让我们从创建三个不同的正则表达式开始,即字母"a"后面跟着?*+。我们来看看当这些表达式与空输入字符串""进行匹配时会发生什么:

 
输入你的正则表达式:a?
输入要搜索的字符串: 
我找到了文本"",从索引0开始,到索引0结束。

输入你的正则表达式:a*
输入要搜索的字符串: 
我找到了文本"",从索引0开始,到索引0结束。

输入你的正则表达式:a+
输入要搜索的字符串: 
未找到匹配项。

零长度匹配

在上面的示例中,第一和第二个情况下匹配成功,因为表达式a?a*都允许字母a出现零次。您还会注意到,起始和结束索引都是零,这与我们之前见到的任何示例都不同。空输入字符串""没有长度,因此测试仅仅在索引0处匹配空字符串。这种类型的匹配称为零长度匹配。零长度匹配可以出现在多种情况下:在空输入字符串中,在输入字符串的开头,在输入字符串的最后一个字符之后,或者在输入字符串的任意两个字符之间。零长度匹配很容易识别,因为它们始终在相同的索引位置开始和结束。

让我们通过几个例子来探索零长度匹配。将输入字符串更改为单个字母"a",你会注意到一些有趣的事情:

 
输入您的正则表达式:a?
输入要搜索的字符串:a
我在索引0处找到文本"a",在索引1处结束。
我在索引1处找到文本"",在索引1处结束。

输入您的正则表达式:a*
输入要搜索的字符串:a
我在索引0处找到文本"a",在索引1处结束。
我在索引1处找到文本"",在索引1处结束。

输入您的正则表达式:a+
输入要搜索的字符串:a
我在索引0处找到文本"a",在索引1处结束。

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

现在将输入字符串更改为连续五个字母"a",你会得到以下结果:

 
输入您的正则表达式:a?
输入要搜索的字符串:aaaaa
我在索引0处找到文本"a",在索引1处结束。
我在索引1处找到文本"a",在索引2处结束。
我在索引2处找到文本"a",在索引3处结束。
我在索引3处找到文本"a",在索引4处结束。
我在索引4处找到文本"a",在索引5处结束。
我在索引5处找到文本"",在索引5处结束。

输入您的正则表达式:a*
输入要搜索的字符串:aaaaa
我在索引0处找到文本"aaaaa",在索引5处结束。
我在索引5处找到文本"",在索引5处结束。

输入您的正则表达式:a+
输入要搜索的字符串:aaaaa
我在索引0处找到文本"aaaaa",在索引5处结束。

表达式a?对每个字符都找到一个匹配,因为它在出现"a"零次或一次时匹配。表达式a*找到了两个单独的匹配:第一个匹配中的所有字母"a",然后是索引5处的最后一个字符之后的零长度匹配。最后,a+匹配所有出现的字母"a",忽略最后一个索引处的"nothing"的存在。

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

让我们来看看:

 
输入您的正则表达式:a?
输入要搜索的输入字符串:ababaaaab
我找到了文本“a”,从索引0开始到索引1结束。
我找到了文本“”,从索引1开始到索引1结束。
我找到了文本“a”,从索引2开始到索引3结束。
我找到了文本“”,从索引3开始到索引3结束。
我找到了文本“a”,从索引4开始到索引5结束。
我找到了文本“a”,从索引5开始到索引6结束。
我找到了文本“a”,从索引6开始到索引7结束。
我找到了文本“a”,从索引7开始到索引8结束。
我找到了文本“”,从索引8开始到索引8结束。
我找到了文本“”,从索引9开始到索引9结束。

输入您的正则表达式:a*
输入要搜索的输入字符串:ababaaaab
我找到了文本“a”,从索引0开始到索引1结束。
我找到了文本“”,从索引1开始到索引1结束。
我找到了文本“a”,从索引2开始到索引3结束。
我找到了文本“”,从索引3开始到索引3结束。
我找到了文本“aaaa”,从索引4开始到索引8结束。
我找到了文本“”,从索引8开始到索引8结束。
我找到了文本“”,从索引9开始到索引9结束。

输入您的正则表达式:a+
输入要搜索的输入字符串:ababaaaab
我找到了文本“a”,从索引0开始到索引1结束。
我找到了文本“a”,从索引2开始到索引3结束。
我找到了文本“aaaa”,从索引4开始到索引8结束。

尽管字母“b”出现在单元格1、3和8中,但输出报告在这些位置上显示零长度匹配。正则表达式a?并不是在特别寻找字母“b”,它只是寻找字母“a”的存在(或不存在)。如果量词允许“a”匹配零次,那么在输入字符串中任何不是“a”的东西都会显示为零长度匹配。剩下的“a”根据前面讨论的规则进行匹配。

要精确匹配一个模式出现的n次,只需在一对花括号中指定次数:

 
输入您的正则表达式:a{3}
输入要搜索的输入字符串:aa
未找到匹配。

输入您的正则表达式:a{3}
输入要搜索的输入字符串:aaa
我找到了文本“aaa”,从索引0开始到索引3结束。

输入您的正则表达式:a{3}
输入要搜索的输入字符串:aaaa
我找到了文本“aaa”,从索引0开始到索引3结束。

在这里,正则表达式a{3}在连续三个字母“a”中进行搜索。第一个测试失败,因为输入字符串中没有足够的“a”来进行匹配。第二个测试中,输入字符串中恰好有3个“a”,触发了匹配。第三个测试也触发了匹配,因为输入字符串开头恰好有3个“a”。在那之后的任何内容对第一个匹配都不相关。如果模式在那之后再次出现,将触发后续的匹配:

 
输入你的正则表达式: a{3}
输入要搜索的字符串: aaaaaaaaa
我找到了文本 "aaa",起始索引为0,结束索引为3。
我找到了文本 "aaa",起始索引为3,结束索引为6。
我找到了文本 "aaa",起始索引为6,结束索引为9。

要求模式至少出现n次,只需在数字后添加逗号:

 
输入你的正则表达式: a{3,}
输入要搜索的字符串: aaaaaaaaa
我找到了文本 "aaaaaaaaa",起始索引为0,结束索引为9。

在相同的输入字符串中,这个测试只找到了一个匹配,因为连续的9个a满足了至少需要3个a的条件。

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

 
输入你的正则表达式: a{3,6} // 查找至少连续3个(但不超过6个)a
输入要搜索的字符串: aaaaaaaaa
我找到了文本 "aaaaaa",起始索引为0,结束索引为6。
我找到了文本 "aaa",起始索引为6,结束索引为9。

在这里,第一个匹配被强制停止在上限为6个字符的位置。第二个匹配包含剩下的字符,恰好是三个a - 这是此匹配允许的最少字符数。如果输入字符串短一个字符,就不会有第二个匹配,因为只剩下两个a。

捕获组和带量词的字符类

到目前为止,我们只测试了包含一个字符的输入字符串的量词。实际上,量词只能附加到一个字符上,所以正则表达式 "abc+" 表示的是 "a,后跟一个或多个b,后跟一个c"。它不表示 "abc" 出现一次或多次。然而,量词也可以附加到字符类捕获组上,例如 [abc]+(一个或多个a或b或c)或 (abc)+(组"abc",一次或多次)。

让我们通过连续指定组(dog)三次来进行说明。

 
输入你的正则表达式: (dog){3}
输入要搜索的字符串: dogdogdogdogdogdog
我找到了文本 "dogdogdog",起始索引为0,结束索引为9。
我找到了文本 "dogdogdog",起始索引为9,结束索引为18。

输入你的正则表达式: dog{3}
输入要搜索的字符串: dogdogdogdogdogdog
未找到匹配项。

在第一个示例中,找到了三个匹配,因为量词应用于整个捕获组。然而,如果去掉括号,匹配将失败,因为量词 {3} 现在只应用于字母 "g"。

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

输入你的正则表达式: [abc]{3}
输入要搜索的字符串: abccabaaaccbbbc
我找到了文本 "abc",起始索引为0,结束索引为3。
我找到了文本 "cab",起始索引为3,结束索引为6。
我找到了文本 "aaa",起始索引为6,结束索引为9。
我找到了文本 "ccb",起始索引为9,结束索引为12。
我找到了文本 "bbc",起始索引为12,结束索引为15。

输入你的正则表达式: abc{3}
输入要搜索的字符串: abccabaaaccbbbc
未找到匹配项。

在第一个示例中,量词{3}应用于整个字符类,而在第二个示例中仅应用于字母"c"。

贪婪、懒惰和占有性量词之间的区别

贪婪量词被称为"贪婪",因为它们强制匹配器在尝试第一次匹配之前读取或"吃掉"整个输入字符串。如果第一次匹配尝试(整个输入字符串)失败,匹配器将从输入字符串中退一字符并重新尝试,重复此过程直到找到匹配项或没有更多字符可以退。根据表达式中使用的量词,它将尝试匹配1个或0个字符。

然而,懒惰量词采取相反的方法:它们从输入字符串的开头开始,然后每次都不情愿地吃掉一个字符以寻找匹配项。它们尝试的最后一件事是整个输入字符串。

最后,占有性量词总是吃掉整个输入字符串,仅尝试一次匹配。与贪婪量词不同,占有性量词永远不会退缩,即使这样做可以使整体匹配成功。

为了说明这一点,考虑输入字符串xfooxxxxxxfoo

 
输入你的正则表达式:.*foo  // 贪婪量词
输入要搜索的字符串:xfooxxxxxxfoo
我找到了文本"xfooxxxxxxfoo",从索引0开始,到索引13结束。

输入你的正则表达式:.*?foo  // 懒惰量词
输入要搜索的字符串:xfooxxxxxxfoo
我找到了文本"xfoo",从索引0开始,到索引4结束。
我找到了文本"xxxxxxfoo",从索引4开始,到索引13结束。

输入你的正则表达式:.*+foo // 占有性量词
输入要搜索的字符串:xfooxxxxxxfoo
未找到匹配项。

第一个示例使用贪婪量词.*来查找"任意",零次或多次出现,后跟字母"f" "o" "o"。由于量词是贪婪的,表达式的.*部分首先吃掉整个输入字符串。此时,整体表达式无法成功,因为最后三个字母("f" "o" "o")已被消耗。因此,匹配器慢慢地每次退一字符,直到最右边的"foo"被排出,此时匹配成功并结束搜索。

然而,第二个示例是懒惰的,所以它首先吃掉"nothing"。因为"foo"不出现在字符串的开头,它被迫吞下第一个字母(一个"x"),这会触发第一个匹配项在0和4处。我们的测试工具继续这个过程直到输入字符串用尽。它在4和13处找到另一个匹配项。

第三个示例无法找到匹配项,因为量词是占有性的。在这种情况下,整个输入字符串被.*+吃掉,没有剩下的部分来满足表达式末尾的"foo"。在希望一次性获取所有内容而不退缩的情况下,请使用占有性量词;在匹配不立即找到的情况下,它将优于等效的贪婪量词。


上一页:预定义字符类
下一页:捕获组