火龙果顿首!
* 这是由《程序员》杂志社评出的,刊登在《程序员》2007 年 3 月刊上。这七种基本技能是:数组,字符串与哈希表、正则表达式、调试、两门语言、一个开发环境、SQL 语言和编写软件的思想。
\d[abc]{2}
的形式表示正则表达式的模式。
java RegexTestHarness这个命令来运行,没有被接受的命令行参数。这个应用会不停地循环执行下去 [3] ,提示用户输入正则表达式和字符串。虽然说使用这个测试用具是可选的,但你会发现它用于探究下文所讨论的测试用例将更为方便。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
import java.io.Console;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public
class RegexTestHarness {
public
static
void main(String[] args) {
Console console = System.
console();
if (console ==
null) {
System.
err.println(
"No console.");
System.
exit(1);
}
while (
true) {
Pattern pattern = Pattern.
compile(console.readLine(
"%nEnter your regex: "));
Matcher matcher = pattern.matcher(console.readLine(
"Enter input string to search: "));
boolean found =
false;
while (matcher.find()) {
console.format(
"I found the text \"%s\" starting at index %d " +
"and ending at index %d.%n",
matcher.group(), matcher.start(), matcher.end());
found =
true;
}
if (!found) {
console.format(
"No match found.%n");
}
}
}
}
在继续下一节之前,确认开发环境支持必需的包,并保存和编译这段代码。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public
class RegexTestHarnessV5 {
public
static
void main(String[] args) {
Scanner scanner =
new Scanner(System.
in);
while (
true) {
System.
out.printf(
"%nEnter your regex: ");
Pattern pattern = Pattern.
compile(scanner.nextLine());
System.
out.printf(
"Enter input string to search: ");
Matcher matcher = pattern.matcher(scanner.nextLine());
boolean found =
false;
while (matcher.find()) {
System.
out.printf(
"I found the text \"%s\" starting at index %d and ending at index %d.%n",
matcher.group(), matcher.start(), matcher.end()
);
found =
true;
}
if (!found) {
System.
out.printf(
"No match found.%n");
}
}
}
}
JDK 1.4 适用的测试用具( RegexTestHarnessV4.java
):
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public
class RegexTestHarnessV4 {
public
static
void main(String[] args)
throws IOException {
BufferedReader br =
new BufferedReader(
new InputStreamReader(
new BufferedInputStream(System.
in))
);
while (
true) {
System.
out.print(
"\nEnter your regex: ");
Pattern pattern = Pattern.
compile(br.readLine());
System.
out.print(
"Enter input string to search: ");
Matcher matcher = pattern.matcher(br.readLine());
boolean found =
false;
while (matcher.find()) {
System.
out.println(
"I found the text \"" + matcher.group() +
"\" starting at index " + matcher.start() +
" and ending at index " + matcher.end() +
".");
found =
true;
}
if (!found) {
System.
out.println(
"No match found.");
}
}
}
}
foo
,输入的字符串也是 foo,这个匹配将会是成功的,因为这两个字符串是相同的。试着用测试用具来测试一下:
Enter your regex: foo Enter input string to search: foo I found the text "foo" starting at index 0 and ending at index 3.结果确实是成功的。注意当输入的字符串是 3 个字符长度的时候,开始的索引是 0,结束的索引是 3。这个是约定俗成的,范围包括开始的索引,不包括结束的索引,如下图所示:
Enter your regex: foo Enter input string to search: foofoofoo I found the text "foo" starting at index 0 and ending at index 3. I found the text "foo" starting at index 3 and ending at index 6. I found the text "foo" starting at index 6 and ending at index 9.
cat.
并输入字符串“cats”,输出如下所示:
Enter your regex: cat. Enter input string to search: cats I found the text "cats" starting at index 0 and ending at index 4.虽然在输入的字符串中没有点(.),但这个匹配仍然是成功的。这是由于点(
.
)是一个
元字符
(metacharacters)(被这个匹配翻译成了具有特殊意义的字符了)。这个例子为什么能匹配成功的原因在于,元字符
.
指的是“任意字符”。
(
[
{
\
^
-
$
|
}
]
)
?
*
+
.
注意:在学习过更多的如何构建正则表达式后,你会碰到这些情况:上面的这些特殊字符不应该被处理为元字符。然而也能够使用这个清单来检查一个特殊的字符是否会被认为是元字符。例如,字符 !、@ 和 # 决不会有特殊的意义。
有两种方法可以强制将元字符处理成为普通字符:\
);
\Q
(引用开始)和
\E
(引用结束)之间
[5]
。在使用这种技术时,
\Q
和
\E
能被放于表达式中的任何位置(假设先出现
\Q
[6]
)
[abc] |
a, b 或 c(简单类) |
[^abc] |
除 a, b 或 c 之外的任意字符(取反) |
[a-zA-Z] |
a 到 z,或 A 到 Z,包括(范围) |
[a-d[m-p]] |
a 到 d,或 m 到 p:[a-dm-p] (并集) |
[a-z&&[def]] |
d,e 或 f(交集) |
[a-z&&[^bc]] |
除 b 和 c 之外的 a 到 z 字符:[ad-z] (差集) |
[a-z&&[^m-p]] |
a 到 z,并且不包括 m 到 p:[a-lq-z] (差集) |
注意:“字符类(character class)”这个词中的“类(class)”指的并不是一个 .class 文件。在正则表达式的语义中,字符类是放在方括号里的字符集,指定了一些字符中的一个能被给定的字符串所匹配。
[bcr]at
会匹配“bat”、“cat”或者“rat”,这是由于其定义了一个字符类(接受“b”、“c”或“r”中的一个字符)作为它的首字符。
Enter your regex: [bcr]at Enter input string to search: bat I found the text "bat" starting at index 0 and ending at index 3. Enter your regex: [bcr]at Enter input string to search: cat I found the text "cat" starting at index 0 and ending at index 3. Enter your regex: [bcr]at Enter input string to search: rat I found the text "rat" starting at index 0 and ending at index 3. Enter your regex: [bcr]at Enter input string to search: hat No match found.在上面的例子中,在第一个字符匹配字符类中所定义字符中的一个时,整个匹配就是成功的。
^
元字符,这种就被称为
否定
(negation)。
Enter your regex: [^bcr]at Enter input string to search: bat No match found. Enter your regex: [^bcr]at Enter input string to search: cat No match found. Enter your regex: [^bcr]at Enter input string to search: rat No match found. Enter your regex: [^bcr]at Enter input string to search: hat I found the text "hat" starting at index 0 and ending at index 3.在输入的字符串中的第一个字符不包含在字符类中所定义字符中的一个时,匹配是成功的。
-
元字符,比如:
[1-5]
或者是
[a-h]
。也可以在类里每个的边上放置不同的范围来提高匹配的可能性,例如:
[a-zA-Z]
将会匹配 a 到 z(小写字母)或者 A 到 Z(大写字母)中的任何一个字符。
Enter your regex: [a-c] Enter input string to search: a I found the text "a" starting at index 0 and ending at index 1. Enter your regex: [a-c] Enter input string to search: b I found the text "b" starting at index 0 and ending at index 1. Enter your regex: [a-c] Enter input string to search: c I found the text "c" starting at index 0 and ending at index 1. Enter your regex: [a-c] Enter input string to search: d No match found. Enter your regex: foo[1-5] Enter input string to search: foo1 I found the text "foo1" starting at index 0 and ending at index 4. Enter your regex: foo[1-5] Enter input string to search: foo5 I found the text "foo5" starting at index 0 and ending at index 4. Enter your regex: foo[1-5] Enter input string to search: foo6 No match found. Enter your regex: foo[^1-5] Enter input string to search: foo1 No match found. Enter your regex: foo[^1-5] Enter input string to search: foo6 I found the text "foo6" starting at index 0 and ending at index 4.
[0-4[6-8]]
,这种奇特方式构建的并集字符类,可以匹配 0,1,2,3,4,6,7,8 这几个数字。
Enter your regex: [0-4[6-8]] Enter input string to search: 0 I found the text "0" starting at index 0 and ending at index 1. Enter your regex: [0-4[6-8]] Enter input string to search: 5 No match found. Enter your regex: [0-4[6-8]] Enter input string to search: 6 I found the text "6" starting at index 0 and ending at index 1. Enter your regex: [0-4[6-8]] Enter input string to search: 8 I found the text "8" starting at index 0 and ending at index 1. Enter your regex: [0-4[6-8]] Enter input string to search: 9 No match found.
[0-9&&[345]]
中那样使用
&&
。这种方式构建出来的
交集
(intersection)简单字符类,仅仅以匹配两个字符类中的 3,4,5 共有部分。
Enter your regex: [0-9&&[345]] Enter input string to search: 3 I found the text "3" starting at index 0 and ending at index 1. Enter your regex: [0-9&&[345]] Enter input string to search: 4 I found the text "4" starting at index 0 and ending at index 1. Enter your regex: [0-9&&[345]] Enter input string to search: 5 I found the text "5" starting at index 0 and ending at index 1. Enter your regex: [0-9&&[345]] Enter input string to search: 2 No match found. Enter your regex: [0-9&&[345]] Enter input string to search: 6 No match found.下面演示两个范围交集的例子:
Enter your regex: [2-8&&[4-6]] Enter input string to search: 3 No match found. Enter your regex: [2-8&&[4-6]] Enter input string to search: 4 I found the text "4" starting at index 0 and ending at index 1. Enter your regex: [2-8&&[4-6]] Enter input string to search: 5 I found the text "5" starting at index 0 and ending at index 1. Enter your regex: [2-8&&[4-6]] Enter input string to search: 6 I found the text "6" starting at index 0 and ending at index 1. Enter your regex: [2-8&&[4-6]] Enter input string to search: 7 No match found.
[0-9&&[^345]]
,这个是构建一个匹配除 3,4,5 之外所有 0 到 9 间数字的简单字符类。
Enter your regex: [0-9&&[^345]] Enter input string to search: 2 I found the text "2" starting at index 0 and ending at index 1. Enter your regex: [0-9&&[^345]] Enter input string to search: 3 No match found. Enter your regex: [0-9&&[^345]] Enter input string to search: 4 No match found. Enter your regex: [0-9&&[^345]] Enter input string to search: 5 No match found. Enter your regex: [0-9&&[^345]] Enter input string to search: 6 I found the text "6" starting at index 0 and ending at index 1. Enter your regex: [0-9&&[^345]] Enter input string to search: 9 I found the text "9" starting at index 0 and ending at index 1.到此为止,已经涵盖了如何建立字符类的部分。在继续下一节之前,可以试着回想一下那张 字符类表 。
. |
任何字符(匹配或者不匹配行结束符) |
\d |
数字字符:[0-9] |
\D |
非数字字符:[^0-9] |
\s |
空白字符:[\t\n\x0B\f\r] |
\S |
非空白字符:[^\s] |
\w |
单词字符:[a-zA-Z_0-9] |
\W |
非单词字符:[^\w] |
\d
指的是数字范围(0~9),
\w
指的是单词字符(任何大小写字母、下划线或者是数字)。无论何时都有可能使用预定义字符类,它可以使代码更易阅读,更易从难看的字符类中排除错误。
\
)开始的构造称为
转义构造
(escaped constructs)。回顾一下在 字符串
一节中的转义构造,在那里我们提及了使用反斜线,以及用于引用的
\Q
和
\E
。在字符串中使用转义构造,必须在一个反斜线前再增加一个反斜用于字符串的编译,例如:
001
private
final String
REGEX =
"\\d";
// 单个数字
这个例子中
\d
是正则表达式,另外的那个反斜线是用于代码编译所必需的。但是测试用具读取的表达式,是直接从控制台中输入的,因此不需要那个多出来的反斜线。
Enter your regex: . Enter input string to search: @ I found the text "@" starting at index 0 and ending at index 1. Enter your regex: . Enter input string to search: 1 I found the text "1" starting at index 0 and ending at index 1. Enter your regex: . Enter input string to search: a I found the text "a" starting at index 0 and ending at index 1. Enter your regex: \d Enter input string to search: 1 I found the text "1" starting at index 0 and ending at index 1. Enter your regex: \d Enter input string to search: a No match found. Enter your regex: \D Enter input string to search: 1 No match found. Enter your regex: \D Enter input string to search: a I found the text "a" starting at index 0 and ending at index 1. Enter your regex: \s Enter input string to search: I found the text " " starting at index 0 and ending at index 1. Enter your regex: \s Enter input string to search: a No match found. Enter your regex: \S Enter input string to search: No match found. Enter your regex: \S Enter input string to search: a I found the text "a" starting at index 0 and ending at index 1. Enter your regex: \w Enter input string to search: a I found the text "a" starting at index 0 and ending at index 1. Enter your regex: \w Enter input string to search: ! No match found. Enter your regex: \W Enter input string to search: a No match found. Enter your regex: \W Enter input string to search: ! I found the text "!" starting at index 0 and ending at index 1.在开始的三个例子中,正则表达式是简单的,
.
(“点”元字符)表示“任意字符”,因此,在所有的三个例子(随意地选取了“@”字符,数字和字母)中都是匹配成功的。在接下来的例子中,都使用了预定义字符类表格中的单个正则表达式构造。你应该可以根据这张表指出前面每个匹配的逻辑:
\d
匹配数字字符
\s
匹配空白字符
\w
匹配单词字符
\D
匹配非数字字符
\S
匹配非空白字符
\W
匹配非单词字符
X
的次数。
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
后面跟着
?
、
*
和
+
。接下来看一下,用这些表达式来测试输入的字符串是空字符串时会发生些什么:
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。输入的空字符串没有长度,因此该测试简单地在索引 0 上匹配什么都没有,诸如此类的匹配称之为
零长度匹配
(zero-length matches)。零长度匹配会出现在以下几种情况:输入空的字符串、在输入字符串的开始处、在输入字符串最后字符的后面,或者是输入字符串中任意两个字符之间。由于它们开始和结束的位置有着相同的索引,因此零长度匹配是容易被发现的。
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 之间的单元格中,并且测试用具一直循环下去直到不再有匹配为止。依赖于所使用的量词不同,最后字符后面的索引“什么也没有”的存在可以或者不可以触发一个匹配。
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”,忽略了在最后索引处“什么都没有”的存在。
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 被匹配。
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 用来匹配;第二次测试输出的字符串正好包括了三个“a”,触发了一次匹配;第三次测试也触发了一次匹配,这是由于在输出的字符串的开始部分正好有三个“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.输入一样的字符串,这次测试仅仅找到了一个匹配,这是由于一个中有九个“a”满足了“至少”三个“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(这是匹配所允许最小的字符个数)。如果输入的字符串再少掉一个字母,这时将不会有第二个匹配,之后仅剩余两个 a。
abc+
的意思就是“a 后面接着 b,再接着一次或者多次的 c”,它的意思并不是指
abc
一次或者多次。然而,量词也可能附在字符类和捕获组的后面,比如,
[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”上。
Enter your regex: .*foo // 贪婪量词 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.第一个例子使用贪婪量词
.*
,寻找紧跟着字母“f”“o”“o”的“任何东西”零次或者多次。由于量词是贪婪的,表达式的
.*
部分第一次“吃掉”整个输入的字符串。在这一点,全部表达式不能成功地进行匹配,这是由于最后三个字母(“f”“o”“o”)已经被消耗掉了。那么匹配器会慢慢地每次回退一个字母,直到返还的“foo”在最右边出现,这时匹配成功并且搜索终止。
.*+
消耗了,什么都没有剩下来满足表达式末尾的“foo”。
(dog)
建了单个的组,包括字符“d”“o”和“g”。匹配捕获组输入的字符串部分将会存放于内存中,稍后通过反向引用再次调用。(在 6.2 节
中将会讨论反向引用)
((A)(B(C)))
中,有下面的四组:
((A)(B(C)))
(A)
(B(C))
(C)
(?
开始的组是纯粹的
非捕获组
(non-capturing group),它不捕获文本,也不作为组总数而计数。(可以看 8 Pattern 类的方法
一节中非捕获组的例子。)
\
)后跟一个表示需要再调用组号的数字来表示。例如,表达式
(\d\d)
定义了匹配一行中的两个数字的捕获组,通过反向引用
\1
,表达式稍候会被再次调用。
(\d\d)\1
作为正则表达式:
Enter your regex: (\d\d)\1 Enter input string to search: 1212 I found the text "1212" starting at index 0 and ending at index 4.如果更改最后的两个数字,这时匹配就会失败:
Enter your regex: (\d\d)\1 Enter input string to search: 1234 No match found.对于嵌套的捕获组而言,反向引用采用完全相同的方式进行工作,即指定一个反斜线加上需要被再次调用的组号。
^ |
行首 |
$ |
行尾 |
\b |
单词边界 |
\B |
非单词边界 |
\A |
输入的开头 |
\G |
上一个匹配的结尾 |
\Z |
输入的结尾,仅用于最后的结束符(如果有的话) |
\z |
输入的结尾 |
^
和
$
边界匹配器的用法。注意上表中,
^
匹配行首,
$
匹配行尾。
Enter your regex: ^dog$ Enter input string to search: dog I found the text "dog" 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 " dog" starting at index 0 and ending at index 15. Enter your regex: ^dog\w* Enter input string to search: dogblahblah I found the text "dogblahblah" 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 "dog" 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 "dog" 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 "dog" starting at index 0 and ending at index 3. I found the text "dog" starting at index 4 and ending at index 7. Enter your regex: \Gdog Enter input string to search: dog dog I found the text "dog" starting at index 0 and ending at index 3.这里的第二个例子仅找到了一个匹配,这是由于第二次出现的“dog”不是在前一个匹配结尾的开始。 [7]
a\u030A
[8]
在指定此标志后,将匹配字符串“\u00E5”(即字符
å
)。默认情况下,匹配不会采用规范等价。指定此标志可能会对性能会有一定的影响。
(?i)
来启用。指定此标志可能会对性能会有一定的影响。
#
开始的直到行尾的内嵌注释会被忽略。注释模式也能通过内嵌标志表达式
(?x)
来启用。
.
匹配包括行结束符在内的任意字符。默认情况下,表达式不会匹配行结束符。dotall 模式也通过内嵌标志表达式
(?x)
来启用。[s 是“单行(single-line)”模式的助记符,与 Perl 中的相同。]
^
和
$
分别匹配输入序列行结束符前面和行结束符的前面。默认情况下,表达式仅匹配整个输入序列的开始和结尾。多行模式也能通过内嵌标志表达式
(?m)
来启用。
(?u)
来启用。指定此标志可能会对性能会有一定的影响。
.
、
^
和
$
的行为仅识别“\n”的行结束符。Unix 行模式可以通过内嵌标志表达式
(?d)
来启用。
001
002
003
004
Pattern pattern = Pattern.
compile(
console.readLine(
"%nEnter your regex: "),
Pttern.
CASE_INSENSITIVE
);
编译并运行这个测试用具,会得出下面的结果:
Enter your regex: dog Enter input string to search: DoGDOg I found the text "DoG" starting at index 0 and ending at index 3. I found the text "DOg" starting at index 3 and ending at index 6.正如你所看到的,不管是否大小写,字符串字面上是“dog”的都产生了匹配。使用多个标志来编译一个模式,使用按位或操作符“|”分隔各个标志。为了更清晰地说明,下面的示例代码使用硬编码(hardcode)的方式,来取代控制台中的读取:
001
pattern = Pattern.
compile(
"[az]$", Pattern.
MULTILINE | Pattern.
UNIX_LINES);
也可以使用一个 int 类型的变量来代替:
001
002
final
int flags = Pattern.
CASE_INSENSITIVE | Pattern.
UNICODE_CASE;
Pattern pattern = Pattern.
compile(
"aa", flags);
(?i)
来启用不区分大小写的匹配。
Enter your regex: (?i)foo Enter input string to search: FOOfooFoOfoO I found the text "FOO" starting at index 0 and ending at index 3. I found the text "foo" starting at index 3 and ending at index 6. I found the text "FoO" starting at index 6 and ending at index 9. I found the text "foO" starting at index 9 and ending at index 12.所有匹配无关大小写都一次次地成功了。
常 量 | 等价的内嵌标志表达式 |
Pattern.CANON_EQ | 没有 |
Pattern.CASE_INSENSITIVE | (?i) |
Pattern.COMMENTS | (?x) |
Pattern.MULTILINE | (?m) |
Pattern.DOTALL | (?s) |
Pattern.LITERAL | 没有 |
Pattern.UNICODE_CASE | (?u) |
Pattern.UNIX_LINES | (?d) |
\d
。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
import java.util.regex.Pattern;
public
class SplitDemo {
private
static
final String
REGEX =
":";
private
static
final String
INPUT =
"one:two:three:four:five";
public
static
void main(String[] args) {
Pattern p = Pattern.
compile(
REGEX);
String[] items = p.split(
INPUT);
for(String s : items) {
System.
out.println(s);
}
}
}
输出:
one two three four five简而言之,已经使用冒号(
:
)取代了复杂的正则表达式匹配字符串文字。以后仍会使用 Pattern 和 Matcher 对象,也能使用 split 得到位于任意正则表达式各边的文本。下面的 SplitDemo2.java
是个一样的例子,使用数字作为 split 的参数:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
import java.util.regex.Pattern;
public
class SplitDemo2 {
private
static
final String
REGEX =
"\\d";
private
static
final String
INPUT =
"one9two4three7four1five";
public
static
void main(String[] args) {
Pattern p = Pattern.
compile(
REGEX);
String[] items = p.split(
INPUT);
for(String s : items) {
System.
out.println(s);
}
}
}
输出:
one two three four five
\
)和美元符号(
$
)将不再有特殊意义了。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public
class MatcherDemo {
private
static
final String
REGEX =
"\\bdog\\b";
private
static
final String
INPUT =
"dog dog dog doggie dogg";
public
static
void main(String[] args) {
Pattern p = Pattern.
compile(
REGEX);
Matcher m = p.matcher(
INPUT);
// 获得匹配器对象
int count = 0;
while (m.find()) {
count++;
System.
out.println(
"Match number " + count);
System.
out.println(
"start(): " + m.start());
System.
out.println(
"end(): " + m.end());
}
}
}
输出:
Match number 1 start(): 0 end(): 3 Match number 2 start(): 4 end(): 7 Match number 3 start(): 8 end(): 11可以看出,这个例子使用了单词边界,用于确保更长单词中的字母“d”“o”“g”就不是子串了。它也输出了一些有用的信息,在输入的字符串中什么地方有匹配。start 方法返回在以前的匹配操作期间,由给定组所捕获子序列的开始处索引,end 方法返回匹配到最后一个字符索引加 1。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public
class MatchesLooking {
private
static
final String
REGEX =
"foo";
private
static
final String
INPUT =
"fooooooooooooooooo";
private
static Pattern pattern;
private
static Matcher matcher;
public
static
void main(String[] args) {
// 初始化
pattern = Pattern.
compile(
REGEX);
matcher = pattern.matcher(
INPUT);
System.
out.println(
"Current REGEX is: " +
REGEX);
System.
out.println(
"Current INPUT is: " +
INPUT);
System.
out.println(
"lookingAt(): " + matcher.lookingAt());
System.
out.println(
"matches(): " + matcher.matches());
}
}
输出:
Current REGEX is: foo Current INPUT is: fooooooooooooooooo lookingAt(): true matches(): false
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public
class ReplaceDemo {
private
static String
REGEX =
"dog";
private
static String
INPUT =
"The dog says meow. All dogs say meow.";
private
static String
REPLACE =
"cat";
public
static
void main(String[] args) {
Pattern p = Pattern.
compile(
REGEX);
Matcher m = p.matcher(
INPUT);
// 获得匹配器对象
INPUT = m.replaceAll(
REPLACE);
System.
out.println(
INPUT);
}
}
输出:
The cat says meow. All cats say meow.在上面的例子中,所有的 dog 都被替换成了 cat。但是为什么在这里停下来了呢?你可以替换匹配任何正则表达式的文本,这样优于替换一个简单的像 dog 一样的文字。这个方法的 API 描述了“给定正则表达式
a*b
,在输入‘aabfooaabfooabfoob’和替换的字符串是‘-’情况下,表达式的匹配器调用方法后,会产生成字符串‘-foo-foo-foo-’。”
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public
class ReplaceDemo2 {
private
static String
REGEX =
"a*b";
private
static String
INPUT =
"aabfooaabfooabfoob";
private
static String
REPLACE =
"-";
public
static
void main(String[] args) {
Pattern p = Pattern.
compile(
REGEX);
Matcher m = p.matcher(
INPUT);
// 获得匹配器对象
INPUT = m.replaceAll(
REPLACE);
System.
out.println(
INPUT);
}
}
输出:
-foo-foo-foo-仅要替换模式一次时,可以简单地调用 replaceFirst 用于取代 replaceAll,它接受相同的参数。
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public
class RegexDemo {
private
static String
REGEX =
"a*b";
private
static String
INPUT =
"aabfooaabfooabfoob";
private
static String
REPLACE =
"-";
public
static
void main(String[] args) {
Pattern p = Pattern.
compile(
REGEX);
Matcher m = p.matcher(
INPUT);
// 获得匹配器对象
StringBuffer sb =
new StringBuffer();
while (m.find()) {
m.appendReplacement(sb,
REPLACE);
}
m.appendTail(sb);
System.
out.println(sb.toString());
}
}
输出:
-foo-foo-foo-
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
import java.io.Console;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.regex.PatternSyntaxException;
public
class RegexTestHarness2 {
public
static
void main(String[] args){
Pattern pattern =
null;
Matcher matcher =
null;
Console console = System.
console();
if (console ==
null) {
System.
err.println(
"No console.");
System.
exit(1);
}
while (
true) {
try {
pattern = Pattern.
compile(console.readLine(
"%nEnter your regex: "));
matcher = pattern.matcher(console.readLine(
"Enter input string to search: "));
}
catch (PatternSyntaxException pse){
console.format(
"There is a problem with the regular expression!%n");
console.format(
"The pattern in question is: %s%n", pse.getPattern());
console.format(
"The description is: %s%n", pse.getDescription());
console.format(
"The message is: %s%n", pse.getMessage());
console.format(
"The index is: %s%n", pse.getIndex());
System.
exit(0);
}
boolean found =
false;
while (matcher.find()) {
console.format(
"I found the text \"%s\" starting at " +
"index %d and ending at index %d.%n",
matcher.group(), matcher.start(), matcher.end()
);
found =
true;
}
if (!found){
console.format(
"No match found.%n");
}
}
}
}
运行该测试,输入
?i)foo
作为正则表达式。这是个臆想出来的错误,程序员在使用内嵌标志表达式
(?i)
时忘记输入左括号了。这样做会产生下面的结果:
Enter your regex: ?i) There is a problem with the regular expression! The pattern in question is: ?i) The description is: Dangling meta character '?' The message is: Dangling meta character '?' near index 0 ?i) ^ The index is: 0从这个输出中,可以看出在索引 0 处的元字符(
?
)附近有语法错误。缺少左括号是导致这个错误的最魁祸首。
\d
、
\s
和
\w
。描述一下它们各表示什么?并使用方括号的形式将它们重写。
\d
、
\s
和
\w
,写出两个简单的表达式,匹配它们相反的字符集。
(dog){3}
,识别一下其中的两个子表达式。这个表达式会匹配什么字符串?
A
是一个普通字符。标点符号
.
是一个元字符,其匹配任意的单字符。
\
);\Q
(开始)\E
(结束)的引用表达式中。\d
、
\s
和
\w
。描述一下它们各表示什么?并使用方括号的形式将它们重写。
\d
匹配任意数字
[0-9]
\s
匹配任意空白字符
[ \t\n-x0B\f\r]
\w
匹配任意单词字符
[a-zA-Z_0-9]
\d
、
\s
和
\w
,写出两个简单的表达式,匹配它们相反的字符集。
\d
\D
[^\d]
\s
\S
[^\s]
\w
\W
[^\w]
(dog){3}
,识别一下其中的两个子表达式。这个表达式会匹配什么字符串?
(dog)
和接着的贪婪量词
{3}
所组成。它匹配字符串“dogdogdog”。
([A-Z][a-zA-Z]*)\s\1
[1] 本文全文译自 Java Tutorial 的 Regular Expressions,标题是译者自拟的。——译者注
[2] Unix 工具,用于文件中的字符串查找,它是最早的正则表达式工具之一。——译者注
[3] 若要退出可以使用 Ctrl + C 来中断。——译者注
[4] 图中的“索引 3”指示是译者所加,原文中并没有。——译者注
[5] 这种方式在 JDK 6.0 以前版本使用需要注意,在字符类中使用这种结构是有 bug 的,不过在 JDK 6.0 中已经修正。——译者注
[6] 若\E
前没有\Q
时会产生 PatternSyntaxException 异常指示语法错误。——译者注
[7] 第一次匹配时仅匹配字符串的开始部分,与\A
类似。(引自 Jeffrey E.F.Friedl, Mastering Regular Expressions, 3rd ed., §3.5.3.3, O'Reilly, 2006.)——译者注
[8] \u030A,即字符 å 上半部分的小圆圈( ̊ )(该字符在 IE 浏览器上无法正确显示,在 Firefox 浏览器上可以正常地显示)。——译者注
[9] JDK 5.0 新增的方法,JDK 1.4 中不能使用。——译者注
[10] JDK 1.4 和 JDK 5.0 适用的版本在所附的源代码中。适用于 JDK 1.4 的文件名为 RegexTestHarness2V4.java,JDK 1.5 的文件名为 RegexTestHarness2V5.java。——译者注
[11] 第三版是本书的最新版本。第三版的中译本《精通正则表达式》已由电子工业出版社于 2007 年 7 月出版。——译者注
|
(选择结构)也没有涉及。对于非捕获组来说,仅仅提到了内嵌标志表达式,对于诸如
(?:X)
、
(?=X)
、
(?!X)
、
(?<=X)
、
(?
、
(?>X)
等等之类的非捕获组结构完全没有涉及。正如译者在序中提到的,这篇文章只为今后学习更高级的正则表达式技术奠定良好的基础。