`
阅读更多
正则表达式是JDK1.4的新功能,但是对sed和awk这样的Unix的标准实用工具,以及Python,Perl之类的语言来讲,它早就已经成为其不可或缺的组成部分了(有人甚至认为,它还是Perl能大获成功的最主要的原因)。单从技术角度来讲,正则表达式只是一种处理字符串的工具(过去Java 这个任务是交由String,StringBuffer以及StringTokenizer处理的),但是它常常和I/O一起使用,所以放到这里来讲也不算太离题吧。[66]

正则表达式是一种功能强大但又非常灵活的文本处理工具。它能让你用编程的方式来描述复杂的文本模式,然后在字符串里把它找出来。一旦你找到了这种模式,你就能随心所欲地处理这些文本了。虽然初看起来正则表达式的语法有点让人望而生畏,但它提供了一种精练的动态语言,使我们能用一种通用的方式来解决各种字符串的问题,包括匹配,选择,编辑以及校验。

创建正则表达式
你可以从比较简单的东西入手学习正则表达式。要想全面地掌握怎样构建正则表达式,可以去看JDK文档的java.util.regex的Pattern类的文档。

字符
B字符B
\xhh16进制值0xhh所表示的字符
\uhhhh16进制值0xhhhh所表示的Unicode字符
\tTab
\n换行符
\r回车符
\f换页符
\eEscape

正则表达式的强大体现在它能定义字符集(characterclass)。下面是一些最常见的字符集及其定义的方式,此外还有一些预定义的字符集:

字符集
.表示任意一个字符
[abc]表示字符a,b,c中的任意一个(与a|b|c相同)
[^abc]除a,b,c之外的任意一个字符(否定)
[a-zA-Z]从a到z或A到Z当中的任意一个字符(范围)
[abc[hij]]a,b,c,h,i,j中的任意一个字符(与a|b|c|h|i|j相同)(并集)
[a-z&&[hij]]h,i,j中的一个(交集)
\s空格字符(空格键,tab,换行,换页,回车)
\S非空格字符([^\s])
\d一个数字,也就是[0-9]
\D一个非数字的字符,也就是[^0-9]
\w一个单词字符(wordcharacter),即[a-zA-Z_0-9]
\W一个非单词的字符,[^\w]

如果你用过其它语言的正则表达式,那么你一眼就能看出反斜杠的与众不同。在其它语言里,"\\"的意思是"我只是要在正则表达式里插入一个反斜杠。没什么特别的意思。"但是在Java里,"\\"的意思是"我要插入一个正则表达式的反斜杠,所以跟在它后面的那个字符的意思就变了。"举例来说,如果你想表示一个或更多的"单词字符",那么这个正则表达式就应该是"\\w+"。如果你要插入一个反斜杠,那就得用"\\\\"。不过像换行,跳格之类的还是只用一根反斜杠:"\n\t"。

这里只给你讲一个例子;你应该JDK文档的java.util.regex.Pattern加到收藏夹里,这样就能很容易地找到各种正则表达式的模式了。

逻辑运算符
XYX后面跟着Y
X|YX或Y
(X)一个"要匹配的组(capturinggroup)".以后可以用\i来表示第i个被匹配的组。

边界匹配符
^一行的开始
$一行的结尾
\b一个单词的边界
\B一个非单词的边界
\G前一个匹配的结束


举一个具体一些的例子。下面这些正则表达式都是合法的,而且都能匹配"Rudolph":

Rudolph
[rR]udolph
[rR][aeiou][a-z]ol.*
R.*
数量表示符
"数量表示符(quantifier)"的作用是定义模式应该匹配多少个字符。

Greedy (贪婪的):除非另有表示,否则数量表示符都是greedy的。Greedy的表达式会一直匹配下去,直到匹配不下去为止。(如果你发现表达式匹配的结果与预期的不符),很有可能是因为,你以为表达式会只匹配前面几个字符,而实际上它是greedy的,因此会一直匹配下去。
Reluctant(勉强的):用问号表示,它会匹配最少的字符。也称为lazy,minimalmatching,non-greedy,或ungreedy。
Possessive (占有的):目前只有Java支持(其它语言都不支持)。它更加先进,所以你可能还不太会用。用正则表达式匹配字符串的时候会产生很多中间状态,(一般的匹配引擎会保存这种中间状态,)这样匹配失败的时候就能原路返回了。占有型的表达式不保存这种中间状6nbsp;
X{n,m}不会回头重来了。它能防止正则表达式的失控,同时也能提高运行的效率。
GreedyReluctantPossessive匹配
X?X??X?+匹配一个或零个X
X*X*?X*+匹配零或多个X
X+X+?X++匹配一个或多个X
X{n}X{n}?X{n}+匹配正好n个X
X{n,}X{n,}?X{n,}+匹配至少n个X
X{n,m}X{n,m}?X{n,m}+匹配至少n个,至多m个X

再提醒一下,要想让表达式照你的意思去运行,你应该用括号把'X'括起来。比方说:

abc+
似乎这个表达式能匹配一个或若干个'abc',但是如果你真的用它去匹配'abcabcabc'的话,实际上只会找到三个字符。因为这个表达式的意思是'ab'后边跟着一个或多个'c'。要想匹配一个或多个完整的'abc',你应该这样:

(abc)+<bad></bad>
CharSequence
JDK1.4定义了一个新的接口,叫CharSequence。它提供了String和StringBuffer这两个类的字符序列的抽象:

interfaceCharSequence{
charAt(inti);
length();
subSequence(intstart,intend);
toString();
}
为了实现这个新的CharSequence接口,String,StringBuffer以及CharBuffer都作了修改。很多正则表达式的操作都要拿CharSequence作参数。

Pattern和Matcher
先给一个例子%8regularexpressions.
//{Args:abcabcabcdefabc"abc+"&nC是否匹配字符串。第一个参数是要匹配的字符串,后面是正则表达式。正则表达式可以有多个。在Unix/Linux环境下,命令行下的正则表达式还必须用引号。

当你创建正则表达式时,可以用这个程序来判断它是不是会按照你的要求工作。

//:c12:TestRegularExpression.java
//Allowsyoutoeaslytryoutregularexpressions.
//{Args:abcabcabcdefabc"abc+""(abc)+""(abc){2,}"}
importjava6nbsp;=1;i<args.length;i++){
&nbsp26nbsp;staticvoidmain(String[]args){
if(args.length<2){
System.out.println("Usage:\n"+
"javaTestRegularExpression"+
"characterSequenceregularExpression+");
System.exit(0);
}
System.out.println("Input:\""+args[0]+"\"");
for(inti=1;i<args.length;i++){
System.out.println(
&nbsp
}
}+"\"");
Patternp=Pattern.compile(args);
Matcherm=p.matcher(args[0]);
while(m.find()){
System.out.println("Match\""+m.group()+
"\"atpositions"+
m.start()+"-"+(m.end()-1));
}
}
}
}///:~


Java 皈regex,input)
以及能返回String数组%的。Pattern对象表示经编译的正则表达式。静态的 compile()方法负责将表示正则表达式的字符串编译成Pattern对象。正如上述例程所示的,只要给Pattern的matcher()方法送一个字符串就能获取一个Matcher对象。此外,Pattern还有一个能快速判断能否在input里面找到regex的(注意,原文有误,漏了方法名)

staticbooleanmatches(regex,input)
以及能返回String数组的split()方法,它能用regex把字符串分例如:

//:c12:FindDemo.java
importjava.util.rege95传一个字符串就能获得Matcher对象了。接下来就能用Matcher的方法来查询匹配的结果了。

booleanmatches()
booleanlookingAt()
booleanfind()
booleanfind(intstart)
matches()的前提是Pattern匹配整个字符串,而lookingAt()的意思是Pattern匹配字符串的开头。

find()
Matcher.find()的功能是发现CharSequence里的,与pattern相匹配的多个字符序列。例如:

//:c12:FindDemo.java
importjava.util.regex.*;
importcom.bruceeckel.simpletest.*;
importjava.util.*;
publici++;
}
monitor.expec3B=newTest();
publicstaticvoidmain(String[]args){
Matcherm=Pattern.compile("\\w+")
.matcher("Eveningisfullofthelinnet'swings");
while(m.find())
System.out.println(m.group());
inti=0;
while(m.find(i)){
System.out.print(m.group()+"");
i++;
}
monitor.expect(newString[]{
"Evening%nbsp;"innetnnetnetetts&nbsp26nbsp;"full",
"of",
"the",
"linnet",
"s",
"wings",
"Eveningveningeningningingnggisissfull"+
"fullulllllofoffthetheheelinnetlinnet"+
"innetnnetnetettsswingswingsingsngsgss"
&nbsp%1达式调用的正则表达式。Group0r />
"\\w+"的意思是"一个或多个单词字符",因此它会将字符串直接分解成单词。find()像一个迭代器,从头到尾扫描一遍字符串。第二个find()是带int参数的,正如你所看到的,它会告诉方法从哪里开始找??即从参数位置开始查找。

Groups
Group是指里用括号括起来的,能被后面的表达式调用的正则表达式。Group0表示整个表达式,group1表示第一个A,但是没能找到group,则返回null。

A(B(C))D
里面有三个group:group0是ABCD,group1是BC,group2是C。

你可以用下述Matcher方法来使用group:

publicintgroupCount()返回matcher对象中的group的数目。不包括group0。

publicStringgroup()返回上次匹配操作(比方说find())的group0(整个匹配)

publicStringgroup(inti)返回上次匹配操作的某个group。如果匹配成功,但是没能找到group,则返回null。

publicintstart(intgroup)返回上次匹配B"Didgyreandgimbleinthewabe.\n"%bsp;intend(intgroup)返回上次匹配所找到的,group的结束位置,最后一个字符的下标加一。

下面我们举一些group的例子:

//:c12:Groups.java
importjava.util.regex.*;
importcom.bruceeckel.simpletest.*;
publicclassGroups{
privatestaticTestmonitor=newTest();
staticpublicfinalStringpoem=
"Twasbrillig,andtheslithytoves\n"+
"Didgyreandgimbleinthewabe.\n"+
"Allmimsyweretheborr />.matcher(poem);
%bsp;rathsoutgrabe.\n\n"+
"BewaretheJabberwock,myson,\n"+
"Thejawsthatbite,theclawsthatcatch.\n"+
"BewaretheJubjubbird,andshun\n"+
"ThefrumiousBandersnatch.";
publicstaticvoidmain(String[]args){
Matcherm=
Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$")
.matcher(poem);
while(m.find()){
&nb2F>"[were][theborogoves,][the][boro)
System.out.print("["+m.group(j)+"]");
System.out.println();
}
monitor.expect(newString[]{
"[theslithytoves]"+
"[the][slithytoves][slithy][toves]",
"[inthewabe.][in][thewabe.][the][wabe.]",
"[weretheborogoves,]"+
"[were][theborogoves,][the][borogoves,]",
"[momeraths&n[frumiousBandersnatch.][frumious][Bandersnatch.]"
D[rathsoutgrabe.][raths][outgrabe.]",
"[Jabberwock,myson,]"+
"[Jabberwock,][myson,][my][son,]",
"[clawsthatcatch.]"+
"[claws][thatcatch.][that][catch.]",
"[bird,andshun][bird,][andshun][and][shun]",
"[ThefrumiousBandersnatch.][The]"+
"[frumiousBandersnatch.][frumious][Bandersnatch.]"
});
}
}///:~


8表达式注意换行符。这一点是由%"Jabberwocky"的第一部分。可以看到这个正则表达式里有很多用括号括起来的group,它是由任意多个连续的非空字符('\S+')和任意多个连续的空格字符('\s+')所组成的,其最终目的是要捕获每行的最后三个单词;'$'表示一行的结尾。但是'$'通常表示整个字符串的结尾,所以这里要明确地告诉正则表达式注意换行符。这一点是由'(?m)' 标志完成的(模式标志会过一%.simpletest.*;
publicclassStartEnd{
privatesta匹配成功,start()会返回此次匹配的开始位置,end()会返回此次匹配的结束位置,即最后一个字符的下标加一。如果之前的匹配不成功(或者没匹配),那么无论是调用start()还是end (),都会引发一个IllegalStateException。下面这段程序还演示了matches()和lookingAt():

//:c12:StartEnd.java
importjava.util.regex.*;
importcom.bruceeckel.simpletest.*;
publicclassStartEnd{
privatestaticTestmonitor=newTest();
publicssp;i<input.length;i++){
&nbsp%ring[]input=newString[]{
"Javahasregularexpressionsin1.4",
"regularexpressionsnowexpressinginJava",
"Javarepressesoracularexpressions"
};
Pattern
p1=Pattern.compile("re\\w*"),
p2=Pattern.compile("Java.*");
for(inti=0;i<input.length;i++){
System.out.println("input"+i+&quotSystem.out.println("m2.find()'"+m2.group()+<br6nbsp></br6nbsp>m1=p1.matcher(input),
m2=p2.matcher(input);
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()+
"'st6quot;end="+m2.end());
&nb"+m2.end());
if(m1.lookingAt())//Noreset()necessary
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())//Noreset()necessary<br6nbsp></br6nbsp>&nbsp29start="
+m1.start()+"end="+m1.end());
if(m2.matches())
System.out.println("m2.matches()start="
+m2.start()+"end="+m2.end());
}
monitor.expect(newString[]{
"input0:Javahasregularexpressionsin1.4",
"m1.find()'regular'start=9&nbs6nbsp;"expressinginJava",
&nd()'ressions'start=20end=28",
"m2.find()'Javahasregularexpressionsin1.4'"+
"start=0end=35",
"m2.lookingAt()start=0end=35",
"m2.matches()start=0end=35",
"input1:regularexpressionsnow"+
"expressinginJava",
"m1.find()'regular'start=0end&nbspF>"m1.find()'ressions'start&nbs;'ressions'start=11end=19",
"m1.find()'ressing'start=27end=34",
"m2.find()'Java'start=38end=42",
"m1.lookingAt()start=0end=7",
"input2:Javarepressesoracularexpressions",
"m1.find()'represses'start=5end=14",
"m1.find()'ressions'start=27end=35",
6则表达式一开始就相匹配的情冲6nbsp;+
"start=0end=35",
"m2.lookingAt()start=0end=35",
"m2.matches()start=0end=35"
});
}
}///:~


注意,只要字符串里有这个模式,find()就能把它给找出来,但是lookingAt()和matches(),只有在字符串与正则表达式一开始就相匹配的情况下才能返回true。matches()成功的前提%9规分解(canonicaldecomposition)"都完全相同8lookingAt()[67]成功的前提是,字符串的开始部分与正则表达式相匹配。

匹配的模式(Patternflags)
compile()方法还有一个版本,它需要一个控制正则表达式的匹配行为的参数:

PatternPattern.compile(Stringregex,intflag)
flag的取值范围如下:编译标志效果
Pattern.CANON_EQ 当且仅当两个字符的"正规分解(canonicaldecomposition)"都完全相同的情况下,才认定匹配。比如用了8这种模式下,匹配时会忽略(正则配"?"。默认情况下,不考虑"规范相等性(canonicalequivalence)"。
Pattern.CASE_INSENSITIVE
(?i)默认情况下,大小写不明感的匹配只适用于US-ASCII字符集。这个标志能让表达式忽略大小写进行匹配。要想对Unicode字符进行大小不明感的匹配,只要将UNICODE_CASE与这个标志合起来就行了。
Pattern.COMMENTS
(?x)在这种模式下,匹配时会忽略(正则表达式里的)空格字符(译者注:不%E'$'分别匹配一行的开始和结束。8达式里的空格,tab,回车之类)。注释从#开始,一直到这行结束。可以通过嵌入式的标志来启用Unix行模式。
Pattern.DOTALL
(?s)在这种模式下,表达式'.'可以匹配任意字符,包括表示一行的结束符。默认情况下,表达式'.'不匹配行的结束符。
Pattern.MULTILINE
(?m)在这种模式下,'^'和'$'分别匹配一行的开始和结束。此外,'^'仍然匹配字符串的开始,'A2,并且与'.','^',以及'$'进行匹下,这两个表达式仅仅匹配字符串的开始和结束。
Pattern.UNICODE_CASE
(?u)在这个模式下,如果你还启用了CASE_INSENSITIVE标志,那么它会对Unicode字符进行大小写不明感的匹配。默认情况下,大小写不明感的匹配只适用于US-ASCII字符集。
Pattern.UNIX_LINES
(?d)在这个模式下,只有'\n'才被认作一行的中止,并且与'.','^',以及'$'进行匹配。

在这些标志里面,Patts.java
importjava.util.regex.*;
importcom.bruceeckel.simpletest.*;
public(4(其中Pattern.COMMENTS还能帮我们把思路理清楚,并且/或者做文档)。注意,你可以用在表达式里插记号的方式来启用绝大多数的模式。这些记号就在上面那张表的各个标志的下面。你希望模式从哪里开始启动,就在哪里插记号。

可以用"OR"('|')运算符把这些标志合使用:

//:c12:ReFlags.java
importjava.util.regex.*;
importcom.bruceeckel.simpletest.*;
publicclassReFlags{
privatestaticTestmonitor&bsp;System.out.println(m.group());
monitor.expect(newStritring[]args){
Patternp=Pattern.compile("^java",
Pattern.CASE_INSENSITIVE|Pattern.MULTILINE);
Matcherm=p.matcher(
"javahasregex\nJavahasregex\n"+
"JAVAhasprettygoodregularexpressions\n"+
"RegularexpressionsareinJava");
while(m.find())
System.out.println(m.group());
monitor.expect(newString[]{
"java",
8D的部分。

split()
所谓分割%nbsp;"JAVA"
});
}
}///:~


这样创建出来的正则表达式就能匹配以"java","Java","JAVA"...开头的字符串了。此外,如果字符串分好几行,那它还会对每一行做匹配(匹配始于字符序列的开始,终于字符序列当中的行结束符)。注意,group()方法仅返回匹配的部分。

split()
所谓分割是指将以正则表达式为界,将字%"This!!unusualuse!!ofexclamation!!points";
&nbsuencecharseq)
String[]split(CharSequencecharseq,intlimit)
这是一种既快又方便地将文本根据一些常见的边界标志分割开来的方法。

//:c12:SplitDemo.java
importjava.util.regex.*;
importcom.bruceeckel.simpletest.*;
importjava.util.*;
publicclassSplitDemo{
privatestaticTestmonitor=newTest();
publicstaticvoidmain(String[]args){
Stringinput=
"This!!unusualuse!!ofexclamation!!points";
System.out.println(Arrays.asList(
&nbsp%3nts]",
"[Aha!,String,Onlydothefirstthree:
System.out.println(Arrays.asList(
Pattern.compile("!!").split(input,3)));
System.out.println(Arrays.asList(
"Aha!Stringhasasplit()builtin!".split("")));
monitor.expect(newString[]{
"[This,unusualuse,ofexclamation,points]",
"[This,unusualuse,ofexclamation!!points]",
"[Aha!,String,has,a,split(),built,in!]"
26nbsp;replacement)将字符串里,第一个与模式%/>
第二个split()会限定分割的次数。

正则表达式是如此重要,以至于有些功能被加进了String类,其中包括split()(已经看到了),matches(),replaceFirst()以及replaceAll()。这些方法的功能同Pattern和Matcher的相同。

替换操作
正则表达式在替换文本方面特别在行。下面就是一些方法:

replaceFirst(Stringreplacement)将字符串里,第一个与模式相匹配的子串替换成replacement。


appendReplacement (StringBuffersbuf,Stringreplacement)对sbuf进行逐次替换,而不是像replaceFirst()或 replaceAll()那样,只替换第一个或全部子串。这是个非常重要的方法,因为它可以调用方法来生成replacement (replaceFirst()和replaceAll()只允许用固定的字符串来充当replacement)。有了这个方法,你就可以编程区分 group,从而实现更强大的替换功能。

调用完appendReplacement()之后!Here'sablockoftexttouseasinput&nbsp?须调用appendTail(StringBuffersbuf,Stringreplacement)。

下面我们来演示一下怎样使用这些替换方法。说明一下,这段程序所处理的字符串是它自己开头部分的注释,是用正则表达式提取出来并加以处理之后再传给替换方法的。

//:c12:TheReplacements.java
importjava.util.regex.*;
importjava.io.*;
importcom.bruceeckel.util.*;
importcom.bruceeckel.simpletest.*;
/*!Here'sablockoftexttouseasinputto
theregularexpressionmatcher.Note&nbnbsp;Pattern.compile("/\\*!(.*)!\\*/",Pattern.DObsp;oftextbylookingfor
thespecialdelimiters,thenprocessthe
extractedblock.!*/
publicclassTheReplacements{
privatestaticTestmonitor=newTest();
publicstaticvoidmain(String[]args)throwsException{
Strings=TextFile.read("TheReplacements.java");
//Matchthespecially-commentedblockoftextabove:
MatchermInput=
Pattern.compile("/\\*!(.*)!\\*/",Pattern.DOTALL)
.matcher(s);
&29;
s=s.replaceFirst("[aeiou]",3Bs=mInput.group(1);//Capturedbyparentheses
//Replacetwoormorespaceswithasinglespace:
s=s.replaceAll("{2,}","");
//Replaceoneormorespacesatthebeginningofeach
//linewithnospaces.MustenableMULTILINEmode:
s=s.replaceAll("(?m)^+","");
System.out.println(s);
s=s.replaceFirst("[aeiou]","(VOWEL1)");
StringBuffersbuf&nbs;"Here'sablockoftexttouse&n3DPattern.compile("[aeiou]");
Matcherm=p.matcher(s);
//Processthefindinformationasyou
//performthereplacements:
while(m.find())
m.appendReplacement(sbuf,m.group().toUpperCase());
//Putintheremainderofthetext:
m.appendTail(sbuf);
System.out.println(sbuf);
monitor.expect(newString[]{
"Here'sablockoftexttouseasinputto",
"thOOkIngfOr",
"thEspEcIp;"firstextracttheblockoftextbylookingfor",
"thespecialdelimiters,thenprocessthe",
"extractedblock.",
"H(VOWEL1)rE'sAblOckOftExttOUsEAsInpUttO",
"thErEgUlArExprEssIOnmAtchEr.NOtEthAtwE'll",
"fIrstExtrActthEblOckOftExtbylOOkIngfOr",
"thEspEcIAldElImItErs,thEnprOcEssthE",
"ExtrActEdblOck."
});
}
}///:~


我们用前面介绍的TextFile.read()方法来打开和读取文件。mInput的功能是匹配'/*!'和'!*/'之间的文本(注意一下分组用的括号)。接下来,我们将所有两个以上的连续空格全都替换成一个,并且将各行开头的空格全都去掉(为了让这个正则表达式能对所有的行,而不仅仅是第一行起作用,必须%用appendReplacement(),它能让你在进行替86String的replaceAll()(这里用它更方便)。注意,由于每个替换只做一次,因此除了预编译Pattern之外,程序没有额外的开销。

replaceFirst()只替换第一个子串。此外,replaceFirst()和 replaceAll()只能用常量(literal)来替换,所以如果你每次替换的时候还要进行一些操作的话,它们是无能为力的。碰到这种情况,你得用appendReplacement(),它能让你在进行替换的时候想写多少代码就写多少。uot;$g"引用已捕获的group,其中'g'表5是选group做处理,也就是用正则表达式把元音字母找出来,然后换成大写的过程。通常你得在完成全部的替换之后才调用appendTail(),但是如果要模仿 replaceFirst()(或"replacen")的效果,你也可以只替换一次就调用appendTail()。它会把剩下的东西全都放进 sbuf。

你还可以在appendReplacement()的replacement参数里用"$g"引用已捕获的group,其中'g'表示group的号码。不过这是为一些比较简单的操作准备的,因而其效果无法与上述程序相比。

reset()
此外,还可以用reset()方法给现有的Matcher对象配上个新的CharSequence。

//:c12:Resetting.java
importjava.util.regex.*;
importjava.io.*;
importcom.bruceeckel.simpletest.*;
publicclassResetting{
privatestaticTestmonitor=newTest();
publicstaticvoidmain(String[]args)throwsException{
Matcherm=Pattern.compile("[frb][aiu][gx]")
.matcher("fixtherugwithbap;"fix",
"rug",
"bag",
"fix",
"rig",
"rag"
});
}
}///:~
"rug",
"bag",
"fix",
"rig",
"rag"
});
}
}///:~


如果不给参数,reset()会把Matcher设到当前字符串的开始处。

正则表达式与JavaI/O
到目前为止,你看到的都是用正则表达式处理静态字符串的例子。下面我们来演示一下怎样用正则表达式扫描文件并且找出匹配的字符串。受Unix的grep启发,我写了个JGrep.java,它需要两个参数:文件名,以及匹配字符串用的正则表达式。它会把匹配这个正则表达式那部分内容及其所属行的行号打印出来。

//:c12:JGrep.java
//Averysimpleversionofthe"grep"program.
//{Args:JGrep.java"\\b[Ssct]\\w+"}
importjava.io.*;
importjava.util.regex.*;
importjava.util.*;
importcom.bruceeckel.util.*;
publicclassJGrep{
&nbsnbsp;p=Pattern.compile(args[1]);
//Iteratethroughthelinesoftheinputfile:
ListIteratorit=newTextFile(args[0]).listIterator();
while(it.hasNext()){
Matcherm=p.matcher((String)it.next());
while>//Iteratethroughthelinesoftheinputfile:
ListIteratorit=newTextFile(args[0]).listIterator();
while(it.hasNext()){
Matcherm=p.matcher((String)it.next());
while(m.find())
System.out.println(it.nextIndex()+":"+
m.group()+":"+m.start());
}
}
}///:~


文件是用TextFile打开的(本章的前半部分讲的)。由于TextFile会把文件的各行放在ArrayList里面,而我们又提取了一个ListIterator,因此我们可以在文件的各行当中自由移动(既能向前也可以向后)。

每行都会有一个Matcher,然后用find()扫描。注意,我们用ListIterator.nextIndex()跟踪行号。

测试参数是JGrep.java和以[Ssct]开头的单词。

还需要StringTokenizer吗?
看到正则表达式能提供这么强大的功能,你可能会怀疑,是ruceeckel.simpletest.*;
importjava.util.*;
publicclassReplacingStringTokenizer{
privatestaticTestmonitor=newTest();
publicstaticvoidmain(String[]args){
Stringinput="ButI'mnotdeadyet!Ifeelhappy!";
StringTokenizerstoke=%>publicclassReplacingStringTokenizer{
privatestaticTestmonitor=newTest();
publicstaticvoidmain(String[]args){
Stringinput="ButI'mnotdeadyet!Ifeelhappy!";
StringTokenizerstoke=newStringTokenizer(input);
while(stoke.hasMoreElements())
System.out.println(stoke.nextToken());
System.out.println(Arrays.asList(input.split("")));
monitor.expect(newString[]{
"But",
"I'm",
"not",
"dead",
"yet!",
"I",
"feel",
"happy!",
"[But,I'm,not,dead,yet!,I,feel,happy!]"
});
}
}///:~


有了正则表达式,你就能用更复杂的模式将字符串分割开来??要是交给StringTokenizer的话,事情会麻烦得多。戚I/O流类库应该能满足你的基本需求:你可以用它来读写控制台,文件,内存,甚至是Internet。你还可以利用继承来创建新的输入和输出类型。你甚至可以利用Java会自动调用对象的toString()方法8你的基本需求:你可以用它来读写控制台,文件,内存,甚至是Internet。你还可以利用继承来创建新的输入和输出类型。你甚至可以利用Java会自动调用对象的toString()方法的特点(Java仅有的"自动类型转换"),通过重新定义这个方法,来对要传给流的对象做一个简单的扩展。

但是Java的I/O流类库及其文档还是留下了一些缺憾。比方说你打开一个文件往里面写东西,但是这个文件已经有了,这么做会把原先的内容给覆盖了。这时要是能有一个异常就好了??有些编程语言能让你规定只能往新建的文件里输出。看来Java是要你用File对象来判断文件是否存在,因为如果你用FileOutputStream或FileWriter的话,文件就会被覆盖了。

我对I/O流类库的评价是比较矛盾的;它确实能干很多事情,而且做到了跨平台。但是如果你不懂d的功能,而其他语言都已经提供了这种功能。

但是,一旦你真正理解了decorator模式,并且能开始灵活运用这个类库的时候,你就能感受到这种设计的好处了。这时多写几行代码就算与8都已经提供了这种功能。

但是,一旦你真正理解了decorator模式,并且能开始灵活运用这个类库的时候,你就能感受到这种设计的好处了。这时多写几行代码就算不了什么了。

如果你觉得不解渴(本章只是做个介绍,没想要面面俱到),可以去看ElliotteRustyHarold写的JavaI/O(O’Reilly,1999)。这本书讲得更深。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics