网址:http://smallwoniu.blog.51cto.com/3911954/1324133
在Sun的Java JDK 1.40版本中,Java自带了支持正则表达式的包,本文就抛砖引玉地介绍了如何使用java.util.regex包。
可粗略估计一下,除了偶尔用Linux的外,其他Linu x用户都会遇到正则表达式。正则表达式是个极端强大工具,而且在字符串模式-匹配和字符串模式-替换方面富有弹性。在Unix世界里,正则表达式几乎没有什么限制,可肯定的是,它应用非常之广泛。
正则表达式的引擎已被许多普通的Unix工具所实现,包括grep,awk,vi和Emacs等。此外,许多使用比较广泛的脚本语言也支持正则表达式,比如Python,Tcl,JavaScript,以及最著名的Perl。
我很早以前就是个Perl方面的黑客,如果你和我一样话,你也会非常依赖你手边的这些强大的text-munging工具。近几年来,像其他程序开发者一样,我也越来越关注Java的开发。
Java作为一种开发语言,有许多值得推荐的地方,但是它一直以来没有自带对正则表达式的支持。直到最近,借助于第三方的类库,Java开始支持正则表达式,但这些第三方的类库都不一致、兼容性差,而且维护代码起来很糟糕。这个缺点,对我选择Java作为首要的开发工具来说,一直是个巨大的顾虑之处。
你可以想象,当我知道Sun的Java JDK 1.40版本包含了java.util.regex(一个完全开放、自带的正则表达式包)时,是多么的高兴!很搞笑的说,我花好些时间去挖掘这个被隐藏起来的宝石。我非常惊奇的是,Java这样的一个很大改进(自带了java.util.regex包)为什么不多公开一点呢?!
最近,Java双脚都跳进了正则表达式的世界。java.util.regex包在支持正则表达也有它的过人之处,另外Java也提供详细的相关说明文档。使得朦朦胧胧的regex神秘景象也慢慢被拨开。有一些正则表达式的构成(可能最显著的是,在于糅合了字符类库)在Perl都找不到。
在regex包中,包括了两个类,Pattern(模式类)和Matcher(匹配器类)。Pattern类是用来表达和陈述所要搜索模式的对象,Matcher类是真正影响搜索的对象。另加一个新的例外类,PatternSyntaxException,当遇到不合法的搜索模式时,会抛出例外。
即使对正则表达式很熟悉,你会发现,通过java使用正则表达式也相当简单。要说明的一点是,对那些被Perl的单行匹配所宠坏的Perl狂热爱好者来说,在使用java的regex包进行替换操作时,会比他们所以前常用的方法费事些。
本文的局限之处,它不是一篇正则表达式用法的完全教程。如果读者要对正则表达进一步了解的话,推荐阅读Jeffrey Frieldl的Mastering Regular Expressions,该书由O’Reilly出版社出版。我下面就举一些例子来教读者如何使用正则表达式,以及如何更简单地去使用它。
/
设计一个简单的表达式来匹配任何电话号码数字可能是比较复杂的事情,原因在于电话号码格式有很多种情况。所有必须选择一个比较有效的模式。比如:(212) 555-1212, 212-555-1212和212 555 1212,某些人会认为它们都是等价的。
首先让我们构成一个正则表达式。为简单起见,先构成一个正则表达式来识别下面格式的电话号码数字:(nnn)nnn-nnnn。
第一步,创建一个pattern对象来匹配上面的子字符串。一旦程序运行后,如果需要的话,可以让这个对象一般化。匹配上面格式的正则表达可以这样构成:(/d{3})/s/d{3}-/d{4},其中/d单字符类型用来匹配从0到9的任何数字,另外{3}重复符号,是个简便的记号,用来表示有3个连续的数字位,也等效于(/d/d/d)。/s也另外一个比较有用的单字符类型,用来匹配空格,比如Space键,tab键和换行符。
是不是很简单?但是,如果把这个正则表达式的模式用在java程序中,还要做两件事。对java的解释器来说,在反斜线字符(/)前的字符有特殊的含义。在java中,与regex有关的包,并不都能理解和识别反斜线字符(/),尽管可以试试看。但为避免这一点,即为了让反斜线字符(/)在模式对象中被完全地传递,应该用双反斜线字符(/)。此外圆括号在正则表达中两层含义,如果想让它解释为字面上意思(即圆括号),也需要在它前面用双反斜线字符(/)。也就是像下面的一样:
//(//d{3}//)//s//d{3}-//d{4}
现在介绍怎样在java代码中实现刚才所讲的正则表达式。要记住的事,在用正则表达式的包时,在你所定义的类前需要包含该包,也就是这样的一行:
import java.util.regex.*;
下面的一段代码实现的功能是,从一个文本文件逐行读入,并逐行搜索电话号码数字,一旦找到所匹配的,然后输出在控制台。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
BufferedReader
in
;
Pattern pattern = Pattern.compile(
"//(//d{3}//)//s//d{3}-//d{4}"
);
in
=
new
BufferedReader(
new
FileReader(
"phone"
));
String
s;
while
((s =
in
.readLine()) !=
null
)
{
Matcher matcher = pattern.matcher(s);
if
(matcher.find())
{
System.out.println(matcher.group());
}
}
in
.close();
|
对那些熟悉用Python或Javascript来实现正则表达式的人来说,这段代码很平常。在Python和Javascript这些语言中,或者其他的语言,这些正则表达式一旦明确地编译过后,你想用到哪里都可以。与Perl的单步匹配相比,看起来多多做了些工作,但这并不很费事。
find()方法,就像你所想象的,用来搜索与正则表达式相匹配的任何目标字符串,group()方法,用来返回包含了所匹配文本的字符串。应注意的是,上面的代码,仅用在每行只能含有一个匹配的电话号码数字字符串时。可以肯定的说,java的正则表达式包能用在一行含有多个匹配目标时的搜索。本文的原意在于举一些简单的例子来激起读者进一步去学习java自带的正则表达式包,所以对此就没有进行深入的探讨。
这相当漂亮吧! 但是很遗憾的是,这仅是个电话号码匹配器。很明显,还有两点可以改进。如果在电话号码的开头,即区位号和本地号码之间可能会有空格。我们也可匹配这些情况,则通过在正则表达式中加入/s?来实现,其中?元字符表示在模式可能有0或1个空格符。
第二点是,在本地号码位的前三位和后四位数字间有可能是空格符,而不是连字号,更有胜者,或根本就没有分隔符,就是7位数字连在一起。对这几种情况,我们可以用(-|)?来解决。这个结构的正则表达式就是转换器,它能匹配上面所说的几种情况。在()能含有管道符|时,它能匹配是否含有空格符或连字符,而尾部的?元字符表示是否根本没有分隔符的情况。
最后,区位号也可能没有包含在圆括号内,对此可以简单地在圆括号后附上?元字符,但这不是一个很好的解决方法。因为它也包含了不配对的圆括号,比如"(555" 或 "555)"。相反,我们可以通过另一种转换器来强迫让电话号码是否带有有圆括号:(/(/d{3}/)|/d{3})。如果我们把上面代码中的正则表达式用这些改进后的来替换的话,上面的代码就成了一个非常有用的电话号码数字匹配器:
Pattern pattern =
Pattern.compile("(//(//d{3}//)|//d{3})//s?//d{3}(-|)?//d{4}");
可以确定的是,你可以自己试着进一步改进上面的代码。
现在看看第二个例子,它是从Friedl的中改编过来的。其功能是用来检查文本文件中是否有重复的单词,这在印刷排版中会经常遇到,同样也是个语法检查器的问题。
匹配单词,像其他的一样,也可以通过好几种的正则表达式来完成。可能最直接的是/b/w+/b,其优点在于只需用少量的regex元字符。其中/w元字符用来匹配从字母a到u的任何字符。+元字符表示匹配匹配一次或多次字符,/b元字符是用来说明匹配单词的边界,它可以是空格或任何一种不同的标点符号(包括逗号,句号等)。
现在,我们怎样来检查一个给定的单词是否被重复了三次?为完成这个任务,需充分利用正则表达式中的所熟知的向后扫描。如前面提到的,圆括号在正则表达式中有几种不同的用法,一个就是能提供组合类型,组合类型用来保存所匹配的结果或部分匹配的结果(以便后面能用到),即使遇到有相同的模式。在同样的正则表达中,可能(也通常期望)不止有一个组合类型。在第n个组合类型中匹配结果可以通过向后扫描来获取到。向后扫描使得搜索重复的单词非常简单:/b(/w+)/s+/1/b。
圆括号形成了一个组合类型,在这个正则表示中它是第一组合类型(也是仅有的一个)。向后扫描/1,指的是任何被/w+所匹配的单词。我们的正则表达式因此能匹配这样的单词,它有一个或多个空格符,后面还跟有一个与此相同的单词。注意的是,尾部的定位类型(/b)必不可少,它可以防止发生错误。如果我们想匹配"Paris in the the spring",而不是匹配"Java's regex package is the theme of this article"。根据java现在的格式,则上面的正则表达式就是:Pattern pattern =Pattern.compile("//b(//w+)//s+//1//b");
最后进一步的修改是让我们的匹配器对大小写敏感。比如,下面的情况:"The the theme of this article is the Java's regex package.",这一点在regex中能非常简单地实现,即通过使用在Pattern类中预定义的静态标志CASE_INSENSITIVE :
Pattern pattern =Pattern.compile("//b(//w+)//s+//1//b",
Pattern.CASE_INSENSITIVE);
有关正则表达式的话题是非常丰富,而且复杂的,用Java来实现也非常广泛,则需要对regex包进行的彻底研究,我们在这里所讲的只是冰山一角。即使你对正则表达式比较陌生,使用regex包后会很快发现它强大功能和可伸缩性。如果你是个来自Perl或其他语言王国的老练的正则表达式的黑客,使用过regex包后,你将会安心地投入到java的世界,而放弃其他的工具,并把java的regex包看成是手边必备的利器。
CharSequence
JDK 1.4定义了一个新的接口,叫CharSequence。它提供了String和StringBuffer这两个类的字符序列的抽象:
1
2
3
4
5
6
|
interface
CharSequence {
charAt(
int
i);
length();
subSequence(
int
start,
int
end);
toString();
}
|
为了实现这个新的CharSequence接口,String,StringBuffer以及CharBuffer都作了修改。很多正则表达式的操作都要拿CharSequence作参数。
Pattern和Matcher
先给一个例子。下面这段程序可以测试正则表达式是否匹配字符串。第一个参数是要匹配的字符串,后面是正则表达式。正则表达式可以有多个。在Unix/Linux环境下,命令行下的正则表达式还必须用引号。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
import
java.util.regex.*;
publicclass TestRegularExpression {
publicstaticvoid main(
String
[] args) {
if
(args.length <
2
) {
System.out.println(
"Usage:/n"
+
"java TestRegularExpression "
+
"characterSequence regularExpression+"
);
System.exit(
0
);
}
System.out.println(
"Input: /"
" + args[0] + "
/
""
);
for
(
int
i =
1
; i < args.length; i++) {
System.out.println(
"Regular expression: /"
" + args[i] + "
/
""
);
Pattern p = Pattern.compile(args[i]);
Matcher m = p.matcher(args[
0
]);
while
(m.find()) {
System.out.println(
"Match /"
" + m.group() +
"/"
at positions " +
m.start() +
"-"
+ (m.end() -
1
));
}
}
}
}
Java的正则表达式是由java.util.regex的Pattern和Matcher类实现的。Pattern对象表示经编译的正则表达式。静态的compile( )方法负责将表示正则表达式的字符串编译成Pattern对象。正如上述例程所示的,只要给Pattern的matcher( )方法送一个字符串就能获取一个Matcher对象。此外,Pattern还有一个能快速判断能否在input里面找到regex的
staticboolean matches(?regex, ?input)
以及能返回String数组的split( )方法,它能用regex把字符串分割开来。
只要给Pattern.matcher( )方法传一个字符串就能获得Matcher对象了。接下来就能用Matcher的方法来查询匹配的结果了。
boolean matches()
boolean lookingAt()
boolean find()
boolean find(int start)
matches( )的前提是Pattern匹配整个字符串,而lookingAt( )的意思是Pattern匹配字符串的开头。
find( )
Matcher.find( )的功能是发现CharSequence里的,与pattern相匹配的多个字符序列。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
import
java.util.regex.*;
import
com.bruceeckel.simpletest.*;
import
java.util.*;
publicclass FindDemo {
privatestatic Test monitor =
new
Test();
publicstaticvoid main(
String
[] args) {
Matcher m = Pattern.compile(
"//w+"
)
.matcher(
"Evening is full of the linnet's wings"
);
while
(m.find())
System.out.println(m.group());
int
i =
0
;
while
(m.find(i)) {
System.out.print(m.group() +
" "
);
i++;
}
monitor.expect(
new
String
[] {
"Evening"
,
"is"
,
"full"
,
"of"
,
"the"
,
"linnet"
,
"s"
,
"wings"
,
"Evening vening ening ning ing ng g is is s full "
+
"full ull ll l of of f the the he e linnet linnet "
+
"innet nnet net et t s s wings wings ings ngs gs s "
});
}
}
|
"//w+"的意思是"一个或多个单词字符",因此它会将字符串直接分解成单词。find( )像一个迭代器,从头到尾扫描一遍字符串。第二个find( )是带int参数的,正如你所看到的,它会告诉方法从哪里开始找——即从参数位置开始查找。
Groups
Group是指里用括号括起来的,能被后面的表达式调用的正则表达式。Group 0 表示整个表达式,group 1表示第一个被括起来的group,以此类推。所以;
A(B(C))D
里面有三个group:group 0是ABCD, group 1是BC,group 2是C。
你可以用下述Matcher方法来使用group:
public int groupCount( )返回matcher对象中的group的数目。不包括group0。
public String group( ) 返回上次匹配操作(比方说find( ))的group 0(整个匹配)
public String group(int i)返回上次匹配操作的某个group。如果匹配成功,但是没能找到group,则返回null。
public int start(int group)返回上次匹配所找到的,group的开始位置。
public int end(int group)返回上次匹配所找到的,group的结束位置,最后一个字符的下标加一。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import
java.util.regex.*;
import
com.bruceeckel.simpletest.*;
publicclass Groups {
privatestatic Test monitor =
new
Test();
staticpublicfinal
String
poem =
"Twas brillig, and the slithy toves/n"
+
"Did gyre and gimble in the wabe./n"
+
"All mimsy were the borogoves,/n"
+
"And the mome raths outgrabe./n/n"
+
"Beware the Jabberwock, my son,/n"
+
"The jaws that bite, the claws that catch./n"
+
"Beware the Jubjub bird, and shun/n"
+
"The frumious Bandersnatch."
;
publicstaticvoid main(
String
[] args) {
Matcher m =
Pattern.compile("(?m)(
.matcher(poem);
while
(m.find()) {
for
(
int
j =
0
; j <= m.groupCount(); j++)
System.out.print(
"["
+ m.group(j) +
"]"
);
System.out.println();
}
monitor.expect(
new
String
[]{
"[the slithy toves]"
+
"[the][slithy toves][slithy][toves]"
,
"[in the wabe.][in][the wabe.][the][wabe.]"
,
"[were the borogoves,]"
+
"[were][the borogoves,][the][borogoves,]"
,
"[mome raths outgrabe.]"
+
"[mome][raths outgrabe.][raths][outgrabe.]"
,
"[Jabberwock, my son,]"
+
"[Jabberwock,][my son,][my][son,]"
,
"[claws that catch.]"
+
"[claws][that catch.][that][catch.]"
,
"[bird, and shun][bird,][and shun][and][shun]"
,
"[The frumious Bandersnatch.][The]"
+
"[frumious Bandersnatch.][frumious][Bandersnatch.]"
});
}
}
|
这首诗是Through the Looking Glass的,Lewis Carroll的"Jabberwocky"的第一部分。可以看到这个正则表达式里有很多用括号括起来的group,它是由任意多个连续的非空字符('/S+')和任意多个连续的空格字符('/s+')所组成的,其最终目的是要捕获每行的最后三个单词;'$'表示一行的结尾。但是'$'通常表示整个字符串的结尾,所以这里要明确地告诉正则表达式注意换行符。这一点是由'(?m)'标志完成的(模式标志会过一会讲解)。
start( )和end( )
如果匹配成功,start( )会返回此次匹配的开始位置,end( )会返回此次匹配的结束位置,即最后一个字符的下标加一。如果之前的匹配不成功(或者没匹配),那么无论是调用start( )还是end( ),都会引发一个IllegalStateException。下面这段程序还演示了matches( )和lookingAt( ):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
import
java.util.regex.*;
import
com.bruceeckel.simpletest.*;
publicclass StartEnd {
privatestatic Test monitor =
new
Test();
publicstaticvoid main(
String
[] args) {
String
[] input =
new
String
[] {
"Java has regular expressions in 1.4"
,
"regular expressions now expressing in Java"
,
"Java represses oracular expressions"
};
Pattern
p1 = Pattern.compile(
"re//w*"
),
p2 = Pattern.compile(
"Java.*"
);
for
(
int
i =
0
; i < input.length; i++) {
System.out.println(
"input "
+ i +
": "
+ input[i]);
Matcher
m1 = p1.matcher(input[i]),
m2 = p2.matcher(input[i]);
while
(m1.find())
System.out.println(
"m1.find() '"
+ m1.group() +
"' start = "
+ m1.start() +
" end = "
+ m1.end());
while
(m2.find())
System.out.println(
"m2.find() '"
+ m2.group() +
"' start = "
+ m2.start() +
" end = "
+ m2.end());
if
(m1.lookingAt())
System.out.println(
"m1.lookingAt() start = "
+ m1.start() +
" end = "
+ m1.end());
if
(m2.lookingAt())
System.out.println(
"m2.lookingAt() start = "
+ m2.start() +
" end = "
+ m2.end());
if
(m1.matches())
System.out.println(
"m1.matches() start = "
+ m1.start() +
" end = "
+ m1.end());
if
(m2.matches())
System.out.println(
"m2.matches() start = "
+ m2.start() +
" end = "
+ m2.end());
}
monitor.expect(
new
String
[] {
"input 0: Java has regular expressions in 1.4"
,
"m1.find() 'regular' start = 9 end = 16"
,
"m1.find() 'ressions' start = 20 end = 28"
,
"m2.find() 'Java has regular expressions in 1.4'"
+
" start = 0 end = 35"
,
"m2.lookingAt() start = 0 end = 35"
,
"m2.matches() start = 0 end = 35"
,
"input 1: regular expressions now "
+
"expressing in Java"
,
"m1.find() 'regular' start = 0 end = 7"
,
"m1.find() 'ressions' start = 11 end = 19"
,
"m1.find() 'ressing' start = 27 end = 34"
,
"m2.find() 'Java' start = 38 end = 42"
,
"m1.lookingAt() start = 0 end = 7"
,
"input 2: Java represses oracular expressions"
,
"m1.find() 'represses' start = 5 end = 14"
,
"m1.find() 'ressions' start = 27 end = 35"
,
"m2.find() 'Java represses oracular expressions' "
+
"start = 0 end = 35"
,
"m2.lookingAt() start = 0 end = 35"
,
"m2.matches() start = 0 end = 35"
});
}
}
|
注意,只要字符串里有这个模式,find( )就能把它给找出来,但是lookingAt( )和matches( ),只有在字符串与正则表达式一开始就相匹配的情况下才能返回true。matches( )成功的前提是正则表达式与字符串完全匹配,而lookingAt( )成功的前提是,字符串的开始部分与正则表达式相匹配。
匹配的模式(Pattern flags)
compile( )方法还有一个版本,它需要一个控制正则表达式的匹配行为的参数:
Pattern Pattern.compile(String regex, int flag)
flag的取值范围如下:
编译标志 | 效果 | Pattern.CANON_EQ | 当且仅当两个字符的"正规分解(canonical decomposition)"都完全相同的情况下,才认定匹配。比如用了这个标志之后,表达式"a/u030A"会匹配"?"。默认情况下,不考虑"规范相等性(canonical equivalence)"。 | Pattern.CASE_INSENSITIVE (?i) | 默认情况下,大小写不明感的匹配只适用于US-ASCII字符集。这个标志能让表达式忽略大小写进行匹配。要想对Unicode字符进行大小不明感的匹配,只要将UNICODE_CASE与这个标志合起来就行了。 | Pattern.COMMENTS (?x) | 在这种模式下,匹配时会忽略(正则表达式里的)空格字符(注:不是指表达式里的"//s",而是指表达式里的空格,tab,回车之类)。注释从#开始,一直到这行结束。可以通过嵌入式的标志来启用Unix行模式。 | Pattern.DOTALL (?s) | 在这种模式下,表达式'.'可以匹配任意字符,包括表示一行的结束符。默认情况下,表达式'.'不匹配行的结束符。 | Pattern.MULTILINE (?m) | 在这种模式下,'^'和'$'分别匹配一行的开始和结束。此外,'^'仍然匹配字符串的开始,'$'也匹配字符串的结束。默认情况下,这两个表达式仅仅匹配字符串的开始和结束。 | Pattern.UNICODE_CASE (?u) | 在这个模式下,如果你还启用了CASE_INSENSITIVE标志,那么它会对Unicode字符进行大小写不明感的匹配。默认情况下,大小写不明感的匹配只适用于US-ASCII字符集。 | Pattern.UNIX_LINES (?d) | 在这个模式下,只有'/n'才被认作一行的中止,并且与'.','^',以及'$'进行匹配。 | | | | |
在这些标志里面,Pattern.CASE_INSENSITIVE,Pattern.MULTILINE,以及Pattern.COMMENTS是
最有用的(其中Pattern.COMMENTS还能帮我们把思路理清楚,并且/或者做文档)。注意,你可以用在表达式
里插记号的方式来启用绝大多数的模式。这些记号就在上面那张表的各个标志的下面。你希望模式从哪里开始
启动,就在哪里插记号。
可以用"OR" ('|')运算符把这些标志合使用://: c12:ReFlags.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import
java.util.regex.*;
import
com.bruceeckel.simpletest.*;
publicclass ReFlags {
privatestatic Test monitor =
new
Test();
publicstaticvoid main(
String
[] args) {
Pattern p = Pattern.compile(
"^java"
,
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher(
"java has regex/nJava has regex/n"
+
"JAVA has pretty good regular expressions/n"
+
"Regular expressions are in Java"
);
while
(m.find())
System.out.println(m.group());
monitor.expect(
new
String
[] {
"java"
,
"Java"
,
"JAVA"
});
}
}
|
这样创建出来的正则表达式就能匹配以"java","Java","JAVA"...开头的字符串了。此外,如果字符串分好几行,那它还会对每一行做匹配(匹配始于字符序列的开始,终于字符序列当中的行结束符)。注意,group( )方法仅返回匹配的部分。
split( )
所谓分割是指将以正则表达式为界,将字符串分割成String数组。
String[] split(CharSequence charseq)
String[] split(CharSequence charseq, int limit)
这是一种既快又方便地将文本根据一些常见的边界标志分割开来的方法。//: c12:SplitDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import
java.util.regex.*;
import
com.bruceeckel.simpletest.*;
import
java.util.*;
publicclass SplitDemo {
privatestatic Test monitor =
new
Test();
publicstaticvoid main(
String
[] args) {
String
input =
"This!!unusual use!!of exclamation!!points"
;
System.out.println(Arrays.asList(
Pattern.compile(
"!!"
).split(input)));
System.out.println(Arrays.asList(
Pattern.compile(
"!!"
).split(input,
3
)));
System.out.println(Arrays.asList(
"Aha! String has a split() built in!"
.split(
" "
)));
monitor.expect(
new
String
[] {
"[This, unusual use, of exclamation, points]"
,
"[This, unusual use, of exclamation!!points]"
,
"[Aha!, String, has, a, split(), built, in!]"
});
}
}
|
第二个split( )会限定分割的次数。
正则表达式是如此重要,以至于有些功能被加进了String类,其中包括split( )(已经看到了),matches( ),replaceFirst( )以及replaceAll( )。这些方法的功能同Pattern和Matcher的相同。
替换操作
正则表达式在替换文本方面特别在行。下面就是一些方法:
replaceFirst(String replacement)将字符串里,第一个与模式相匹配的子串替换成replacement。
replaceAll(String replacement),将输入字符串里所有与模式相匹配的子串全部替换成replacement。
appendReplacement(StringBuffer sbuf, String replacement)对sbuf进行逐次替换,而不是像replaceFirst( )或replaceAll( )那样,只替换第一个或全部子串。这是个非常重要的方法,因为它可以调用方法来生成replacement(replaceFirst( )和replaceAll( )只允许用固定的字符串来充当replacement)。有了这个方法,你就可以编程区分group,从而实现更强大的替换功能。
调用完appendReplacement( )之后,为了把剩余的字符串拷贝回去,必须调用appendTail(StringBuffer sbuf, String replacement)。
下面我们来演示一下怎样使用这些替换方法。说明一下,这段程序所处理的字符串是它自己开头部分的注释,是用正则表达式提取出来并加以处理之后再传给替换方法的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
import
java.util.regex.*;
import
java.io.*;
import
com.bruceeckel.util.*;
import
com.bruceeckel.simpletest.*;
publicclass TheReplacements {
privatestatic Test monitor =
new
Test();
publicstaticvoid main(
String
[] args) throws Exception {
String
s = TextFile.read(
"TheReplacements.java"
);
Matcher mInput =
Pattern.compile(
"///*!(.*)!//*/"
, Pattern.DOTALL)
.matcher(s);
if
(mInput.find())
s = mInput.group(
1
);
s = s.replaceAll(
" {2,}"
,
" "
);
s = s.replaceAll(
"(?m)^ +"
,
""
);
System.out.println(s);
s = s.replaceFirst(
"[aeiou]"
,
"(VOWEL1)"
);
StringBuffer sbuf =
new
StringBuffer();
Pattern p = Pattern.compile(
"[aeiou]"
);
Matcher m = p.matcher(s);
while
(m.find())
m.appendReplacement(sbuf, m.group().toUpperCase());
m.appendTail(sbuf);
System.out.println(sbuf);
monitor.expect(
new
String
[]{
"Here's a block of text to use as input to"
,
"the regular expression matcher. Note that we'll"
,
"first extract the block of text by looking for"
,
"the special delimiters, then process the"
,
"extracted block. "
,
"H(VOWEL1)rE's A blOck Of tExt tO UsE As InpUt tO"
,
"thE rEgUlAr ExprEssIOn mAtchEr. NOtE thAt wE'll"
,
"fIrst ExtrAct thE blOck Of tExt by lOOkIng fOr"
,
"thE spEcIAl dElImItErs, thEn prOcEss thE"
,
"ExtrActEd blOck. "
});
}
}
|
用TextFile.read( )方法来打开和读取文件。mInput的功能是匹配'/*!' 和 '!*/' 之间的文本(注意一下分组用的括号)。接下来,我们将所有两个以上的连续空格全都替换成一个,并且将各行开头的空格全都去掉(为了让这个正则表达式能对所有的行,而不仅仅是第一行起作用,必须启用多行模式)。这两个操作都用了String的replaceAll( )(这里用它更方便)。注意,由于每个替换只做一次,因此除了预编译Pattern之外,程序没有额外的开销。
replaceFirst( )只替换第一个子串。此外,replaceFirst( )和replaceAll( )只能用常量(literal)来替换,所以如果每次替换的时候还要进行一些操作的话,它们是无能为力的。碰到这种情况,得用appendReplacement( ),它能在进行替换的时候想写多少代码就写多少。在上面那段程序里,创建sbuf的过程就是选group做处理,也就是用正则表达式把元音字母找出来,然后换成大写的过程。通常你得在完成全部的替换之后才调用appendTail( ),但是如果要模仿replaceFirst( )(或"replace n")的效果,你也可以只替换一次就调用appendTail( )。它会把剩下的东西全都放进sbuf。
你还可以在appendReplacement( )的replacement参数里用"$g"引用已捕获的group,其中'g' 表示group的号码。不过这是为一些比较简单的操作准备的,因而其效果无法与上述程序相比。
reset( )
此外,还可以用reset( )方法给现有的Matcher对象配上个新的CharSequence。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
import
java.util.regex.*;
import
java.io.*;
import
com.bruceeckel.simpletest.*;
publicclass Resetting {
privatestatic Test monitor =
new
Test();
publicstaticvoid main(
String
[] args) throws Exception {
Matcher m = Pattern.compile(
"[frb][aiu][gx]"
)
.matcher(
"fix the rug with bags"
);
while
(m.find())
System.out.println(m.group());
m.reset(
"fix the rig with rags"
);
while
(m.find())
System.out.println(m.group());
monitor.expect(
new
String
[]{
"fix"
,
"rug"
,
"bag"
,
"fix"
,
"rig"
,
"rag"
});
}
}
|
如果不给参数,reset( )会把Matcher设到当前字符串的开始处。
|