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 \starting at index 0 and ending at index 3.
I found the text \starting at index 3 and ending at index 6.
I found the text \starting at index 6 and ending at index 9.
I found the text \starting at index 9 and ending at index 12.
I found the text \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”上。
5.3 贪婪、勉强和侵占量词间的不同返回目录 在贪婪、勉强和侵占三个量词间有着细微的不同。
贪婪量词之所以称之为“贪婪的”,这是由于它们强迫匹配器读入(或者称之为吃掉)整个输入的字符串,来优先尝试第一次匹配,如果第一次尝试匹配(对于整个输入的字符串)失败,匹配器会通过回退整个字符串的一个字符再一次进行尝试,不断地进行处理直到找到一个匹配,或者左边没有更多的字符来用于回退了。赖于在表达式中使用的量词,最终它将尝试地靠着 1 或 0 个字符的匹配。
但是,勉强量词采用相反的途径:从输入字符串的开始处开始,因此每次勉强地吞噬一个字符来寻找匹配,最终它们会尝试整个输入的字符串。
最后,侵占量词始终是吞掉整个输入的字符串,尝试着一次(仅有一次)匹配。不像贪婪量词那样,侵占量词绝不会回退,即使这样做是允许全部的匹配成功。 为了说明一下,看看输入的字符串是 xfooxxxxxxfoo 时。
Enter your regex: .*foo // 贪婪量词
Enter input string to search: xfooxxxxxxfoo
I found the text \ending at index 13.
Enter your regex: .*?foo // 勉强量词
Enter input string to search: xfooxxxxxxfoo I found the text \starting at index 0 and ending at index 4.
I found the text \at index 13.
Enter your regex: .*+foo // 侵占量词
Enter input string to search: xfooxxxxxxfoo No match found. 第一个例子使用贪婪量词.*,寻找紧跟着字母“f”“o”“o”的“任何东西”零次或者多次。由于量词是贪婪的,表达式的.*部分第一次“吃掉”整个输入的字符串。在这一点,全部表达式不能成功地进行匹配,这是由于
最后三个字母(“f”“o”“o”)已经被消耗掉了。那么匹配器会慢慢地每次回退一个字母,直到返还的“foo”在最右边出现,这时匹配成功并且搜索终止。
然而,第二个例子采用勉强量词,因此通过首次消耗“什么也没有”作为开始。由于“foo”并没有出现在字符串的开始,它被强迫吞掉第一个字母(“x”),在 0 和 4 处触发了第一个匹配。测试用具会继续处理,直到输入的字符串耗尽为止。在 4 和 13 找到了另外一个匹配。
第三个例子的量词是侵占,所以在寻找匹配时失败了。在这种情况下,整个输入的字符串被.*+消耗了,什么都没有剩下来满足表达式末尾的“foo”。
你可以在想抓取所有的东西,且决不回退的情况下使用侵占量词,在这种匹配不是立即被发现的情况下,它将会优于等价的贪婪量词。 6 捕获组返回目录
在上一节中,学习了每次如何把量词放在一个字符、字符类或者捕获组中。到目前为止,还没有详细地讨论过捕获组的概念。
捕获组(capturing group)是将多个字符作为单独的单元来对待的一种方式。构建它们可以通过把字符放在一对圆括号中而成为一组。例如,正则表达式(dog)建了单个的组,包括字符“d”“o”和“g”。匹配捕获组输入的字符串部分将会存放于内存中,稍后通过反向引用再次调用。(在 6.2 节 中将会讨论反向引用)
6.1 编号方式返回目录
在 Pattern 的 API 描述中,捕获组通过从左至右计算开始的圆括号进行编号。例如,在表达式
((A)(B(C)))中,有下面的四组: 1. ((A)(B(C))) 2. (A)
3. (B(C)) 4. (C)
要找出当前的表达式中有多少组,通过调用 Matcher 对象的 groupCount 方法。groupCount 方法返回 int 类型值,表示当前 Matcher 模式中捕获组的数量。例如,groupCount 返回 4 时,表示模式中包含有 4 个捕获组。
有一个特别的组——组 0,它表示整个表达式。这个组不包括在 groupCount 的报告范围内。以(?开始的组是纯粹的非捕获组(non-capturing group),它不捕获文本,也不作为组总数而计数。(可以看 8 Pattern 类的方法 一节中非捕获组的例子。)
Matcher 中的一些方法,可以指定 int 类型的特定组号作为参数,因此理解组是如何编号的是尤为重要的。
public int start(int group):返回之前的匹配操作期间,给定组所捕获的子序列的初始索引。 public int end(int group):返回之前的匹配操作期间,给定组所捕获子序列的最后字符索引加 1。 public String group (int group):返回之前的匹配操作期间,通过给定组而捕获的输入子序列。 6.2 反向引用返回目录
匹配输入字符串的捕获组部分会存放在内存中,通过反向引用(backreferences)稍后再调用。在正则表达式中,反向引用使用反斜线(\\)后跟一个表示需要再调用组号的数字来表示。例如,表达式(\\d\\d)定义了匹配一行中的两个数字的捕获组,通过反向引用\\1,表达式稍候会被再次调用。
匹配两个数字,且后面跟着两个完全相同的数字时,就可以使用(\\d\\d)\\1作为正则表达式:
Enter your regex: (\\d\\d)\\1
Enter input string to search: 1212 I found the text \starting at index 0 and ending at index 4.
如果更改最后的两个数字,这时匹配就会失败:
Enter your regex: (\\d\\d)\\1
Enter input string to search: 1234 No match found.
对于嵌套的捕获组而言,反向引用采用完全相同的方式进行工作,即指定一个反斜线加上需要被再次调用的组号。
7 边界匹配器返回目录
就目前而言,我们的兴趣在于指定输入字符串中某些位置是否有匹配,还没有考虑到字符串的匹配产生在什么地方。
通过指定一些边界匹配器(boundary matchers)的信息,可以使模式匹配更为精确。比如说你对某个特定的单词感兴趣,并且它只出现在行首或者是行尾时。又或者你想知道匹配发生在单词边界(word boundary),或者是上一个匹配的尾部。 下表中列出了所有的边界匹配器及其说明。
边界匹配器
^ 行首 $ 行尾 \\b单 词边界 非单词边界 \\B
输入的开头 \\A
上一个匹配的结尾 \\G
输入的结尾,仅用于最后的结束符(如果有的话) \\Z \\z输 入的结尾
接下来的例子中,说明了^和$边界匹配器的用法。注意上表中,^匹配行首,$匹配行尾。
Enter your regex: ^dog$
Enter input string to search: dog
I found the text \starting at index 0 and ending at index 3.
Enter your regex: ^dog$
Enter input string to search: dog No match found.
Enter your regex: \\s*dog$
Enter input string to search: dog
I found the text \ending at index 15.
Enter your regex: ^dog\\w*
Enter input string to search: dogblahblah
I found the text \starting at index 0 and ending at index 11.
第一个例子的匹配是成功的,这是因为模式占据了整个输入的字符串。第二个例子失败了,是由于输入的字符串在开始部分包含了额外的空格。第三个例子指定的表达式是不限的空格,后跟着在行尾的 dog。第四个例子,需要 dog 放在行首,后面跟的是不限数量的单词字符。
对于检查一个单词开始和结束的边界模式(用于长字符串里子字符串),这时可以在两边使用\\b,例如\\bdog\\b。
Enter your regex: \\bdog\\b
Enter input string to search: The dog plays in the yard. I found the text \starting at index 4 and ending at index 7.
Enter your regex: \\bdog\\b
Enter input string to search: The doggie plays in the yard. No match found.
对于匹配非单词边界的表达式,可以使用\\B来代替:
Enter your regex: \\bdog\\B
Enter input string to search: The dog plays in the yard. No match found.
Enter your regex: \\bdog\\B
Enter input string to search: The doggie plays in the yard. I found the text \starting at index 4 and ending at index 7.
对于需要匹配仅出现在前一个匹配的结尾,可以使用\\G:
Enter your regex: dog
Enter input string to search: dog dog
I found the text \starting at index 0 and ending at index 3.
I found the text \starting at index 4 and ending at index 7.
Enter your regex: \\Gdog
Enter input string to search: dog dog
I found the text \starting at index 0 and ending at index 3.
这里的第二个例子仅找到了一个匹配,这是由于第二次出现的“dog”不是在前一个匹配结尾的开始。[7] 8 Pattern 类的方法返回目录
到目前为止,仅使用测试用具来建立最基本的 Pattern 对象。在这一节中,我们将探讨一些诸如使用标志构建模式、使用内嵌标志表达式等高级的技术。同时也探讨了一些目前还没有讨论过的其他有用的方法。
8.1 使用标志构建模式返回目录
Pattern 类定义了备用的 compile 方法,用于接受影响模式匹配方式的标志集。标志参数是一个位掩码,可以是下面公共静态字段中的任意一个: Pattern.CANON_EQ
启用规范等价。在指定此标志后,当且仅当在其完整的规范分解匹配时,两个字符被视为匹配。例如,表达式a\?[8]在指定此标志后,将匹配字符串“\?”(即字符 ?)。默认情况下,匹配不会采用规范等价。指定此标志可能会对性能会有一定的影响。 Pattern.CASE_INSENSITIVE
启用不区分大小写匹配。默认情况下,仅匹配 US-ASCII 字符集中的字符。Unicode 感知
(Unicode-aware)的不区分大小写匹配,可以通过指定 UNICODE_CASE 标志连同此标志来启用。不区分大小写匹配也能通过内嵌标志表达式(?i)来启用。指定此标志可能会对性能会有一定的影响。 Pattern.COMMENTS
模式中允许存在空白和注释。在这种模式下,空白和以#开始的直到行尾的内嵌注释会被忽略。注释模式也能通过内嵌标志表达式(?x)来启用。 Pattern.DOTALL
启用 dotall 模式。在 dotall 模式下,表达式.匹配包括行结束符在内的任意字符。默认情况下,表达式不会匹配行结束符。dotall 模式也通过内嵌标志表达式(?x)来启用。[s 是“单行(single-line)”模式的助记符,与 Perl 中的相同。] Pattern.LITERAL
启用模式的字面分析。指定该标志后,指定模式的输入字符串作为字面上的字符序列来对待。输入序列中的元字符和转义字符不具有特殊的意义了。CASE_INSENSITIVE 和 UNICODE_CASE 与此标志一起使用时,会对匹配产生一定的影响。其他的标志就变得多余了。启用字面分析没有内嵌标志表达式。 Pattern.MULTILINE
启用多行(multiline)模式。在多行模式下,表达式^和$分别匹配输入序列行结束符前面和行结束符的前面。默认情况下,表达式仅匹配整个输入序列的开始和结尾。多行模式也能通过内嵌标志表达式(?m)来启用。
Pattern.UNICODE_CASE
启用可折叠感知 Unicode(Unicode-aware case folding)大小写。在指定此标志后,需要通过 CASE_INSENSITIVE 标志来启用,不区分大小写区配将在 Unicode 标准的意义上来完成。默认情况下,不区分大小写匹配仅匹配 US-ASCII 字符集中的字符。可折叠感知 Unicode 大小写也能通过内嵌标志表达式(?u)来启用。指定此标志可能会对性能会有一定的影响。 Pattern.UNIX_LINES
启用 Unix 行模式。在这种模式下,.、^和$的行为仅识别“\\n”的行结束符。Unix 行模式可以通过内嵌标志表达式(?d)来启用。
接下来,将修改测试用具 RegexTestHarness.java,用于构建不区分大小写匹配的模式。 首先,修改代码去调用 complie 的另外一个备用的方法:
001 002 003 004
Pattern pattern = Pattern.compile(
console.readLine(\