学习Java的同学注意了!!!
学习进程中遇到甚么问题或想获得学习资源的话,欢迎加入Java学习交换群,群号码:183993990 我们1起学Java!
在Sun的Java JDK 1.40版本中,Java自带了支持正则表达式的包,本文就抛砖引玉地介绍了如何使用java.util.regex包。
可粗略估计1下,除偶尔用Linux的外,其他Linu x用户都会遇到正则表达式。正则表达式是个极端强大工具,而且在字符串模式-匹配和字符串模式-替换方面富有弹性。在Unix世界里,正则表达式几近没有甚么限制,可肯定的是,它利用非常之广泛。
正则表达式的引擎已被许多普通的Unix工具所实现,包括grep,awk,vi和Emacs等。另外,许多使用比较广泛的脚本语言也支持正则表达式,比如Python,Tcl,JavaScript,和最著名的Perl。
我很早之前就是个Perl方面的黑客,如果你和我1样话,你也会非常依赖你手边的这些强大的text-munging工具。近几年来,像其他程序开发者1样,我也愈来愈关注Java的开发。
Java作为1种开发语言,有许多值得推荐的地方,但是它1直以来没有自带对正则表达式的支持。直到最近,借助于第3方的类库,Java开始支持正则表达式,但这些第3方的类库都不1致、兼容性差,而且保护代码起来很糟。这个缺点,对我选择Java作为重要的开发工具来讲,1直是个巨大的顾虑的地方。
你可以想象,当我知道Sun的Java JDK 1.40版本包括了java.util.regex(1个完全开放、自带的正则表达式包)时,是多么的高兴!很弄笑的说,我花好些时间去发掘这个被隐藏起来的宝石。我非常惊奇的是,Java这样的1个很大改进(自带了java.util.regex包)为何不多公然1点呢?!
最近,Java双脚都跳进了正则表达式的世界。java.util.regex包在支持正则表达也有它的过人的地方,另外Java也提供详细的相干说明文档。使得朦朦胧胧的regex神秘景象也渐渐被扒开。有1些正则表达式的构成(可能最显著的是,在于糅合了字符类库)在Perl都找不到。
在regex包中,包括了两个类,Pattern(模式类)和Matcher(匹配器类)。Pattern类是用来表达和陈说所要搜索模式的对象,Matcher类是真正影响搜索的对象。另加1个新的例外类,PatternSyntaxException,当遇到不合法的搜索模式时,会抛出例外。
即便对正则表达式很熟习,你会发现,通过java使用正则表达式也相当简单。要说明的1点是,对那些被Perl的单行匹配所宠坏的Perl狂酷爱好者来讲,在使用java的regex包进行替换操作时,会比他们所之前经常使用的方法费事些。
本文的局限的地方,它不是1篇正则表达式用法的完全教程。如果读者要对正则表达进1步了解的话,推荐浏览Jeffrey Frieldl的Mastering Regular Expressions,该书由O’Reilly出版社出版。我下面就举1些例子来教读者如何使用正则表达式,和如何更简单地去使用它。
设计1个简单的表达式来匹配任何电话号码数字多是比较复杂的事情,缘由在于电话号码格式有很多种情况。所有必须选择1个比较有效的模式。比如:(212) 555⑴212, 212⑸55⑴212和212 555 1212,某些人会认为它们都是等价的。
首先让我们构成1个正则表达式。为简单起见,先构成1个正则表达式来辨认下面格式的电话号码数字:(nnn)nnn-nnnn。
第1步,创建1个pattern对象来匹配上面的子字符串。1旦程序运行后,如果需要的话,可让这个对象1般化。匹配上面格式的正则表达可以这样构成:(/d{3})/s/d{3}-/d{4},其中/d单字符类型用来匹配从0到9的任何数字,另外{3}重复符号,是个简便的记号,用来表示有3个连续的数字位,也等效于(/d/d/d)。/s也另外1个比较有用的单字符类型,用来匹配空格,比如Space键,tab键和换行符。
是否是很简单?但是,如果把这个正则表达式的模式用在java程序中,还要做两件事。对java的解释器来讲,在反斜线字符(/)前的字符有特殊的含义。在java中,与regex有关的包,其实不都能理解和辨认反斜线字符(/),虽然可以试试看。但为避免这1点,即为了让反斜线字符(/)在模式对象中被完全地传递,应当用双反斜线字符(/)。另外圆括号在正则表达中两层含义,如果想让它解释为字面上意思(即圆括号),也需要在它前面用双反斜线字符(/)。也就是像下面的1样:
//(//d{3}//)//s//d{3}-//d{4}
现在介绍怎样在java代码中实现刚才所讲的正则表达式。要记住的事,在用正则表达式的包时,在你所定义的类前需要包括该包,也就是这样的1行:
import java.util.regex.*;
下面的1段代码实现的功能是,从1个文本文件逐行读入,并逐行搜索电话号码数字,1旦找到所匹配的,然后输出在控制台。
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这些语言中,或其他的语言,这些正则表达式1旦明确地编译过后,你想用到哪里都可以。与Perl的单步匹配相比,看起来多多做了些工作,但这其实不很费事。
find()方法,就像你所想象的,用来搜索与正则表达式相匹配的任何目标字符串,group()方法,用来返回包括了所匹配文本的字符串。应注意的是,上面的代码,仅用在每行只能含有1个匹配的电话号码数字字符串时。可以肯定的说,java的正则表达式包能用在1行含有多个匹配目标时的搜索。本文的原意在于举1些简单的例子来激起读者进1步去学习java自带的正则表达式包,所以对此就没有进行深入的探讨。
这相当漂亮吧! 但是很遗憾的是,这仅是个电话号码匹配器。很明显,还有两点可以改进。如果在电话号码的开头,即区位号和本地号码之间可能会有空格。我们也可匹配这些情况,则通过在正则表达式中加入/s?来实现,其中?元字符表示在模式可能有0或1个空格符。
第2点是,在本地号码位的前3位和后4位数字间有多是空格符,而不是连字号,更有胜者,或根本就没有分隔符,就是7位数字连在1起。对这几种情况,我们可以用(-|)?来解决。这个结构的正则表达式就是转换器,它能匹配上面所说的几种情况。在()能含有管道符|时,它能匹配是不是含有空格符或连字符,而尾部的?元字符表示是不是根本没有分隔符的情况。
最后,区位号也可能没有包括在圆括号内,对此可以简单地在圆括号后附上?元字符,但这不是1个很好的解决方法。由于它也包括了不配对的圆括号,比如"(555" 或 "555)"。相反,我们可以通过另外一种转换器来逼迫让电话号码是不是带有有圆括号:(/(/d{3}/)|/d{3})。如果我们把上面代码中的正则表达式用这些改进后的来替换的话,上面的代码就成了1个非常有用的电话号码数字匹配器:
Pattern pattern =
Pattern.compile("(//(//d{3}//)|//d{3})//s?//d{3}(-|)?//d{4}");
可以肯定的是,你可以自己试着进1步改进上面的代码。
现在看看第2个例子,它是从Friedl的中改编过来的。其功能是用来检查文本文件中是不是有重复的单词,这在印刷排版中会常常遇到,一样也是个语法检查器的问题。
匹配单词,像其他的1样,也能够通过好几种的正则表达式来完成。可能最直接的是/b/w+/b,其优点在于只需用少许的regex元字符。其中/w元字符用来匹配从字母a到u的任何字符。+元字符表示匹配匹配1次或屡次字符,/b元字符是用来讲明匹配单词的边界,它可以是空格或任何1种不同的标点符号(包括逗号,句号等)。
现在,我们怎样来检查1个给定的单词是不是被重复了3次?为完成这个任务,需充分利用正则表达式中的所熟知的向后扫描。如前面提到的,圆括号在正则表达式中有几种不同的用法,1个就是能提供组合类型,组合类型用来保存所匹配的结果或部份匹配的结果(以便后面能用到),即便遇到有相同的模式。在一样的正则表达中,可能(也通常期望)不止有1个组合类型。在第n个组合类型中匹配结果可以通过向后扫描来获得到。向后扫描使得搜索重复的单词非常简单:/b(/w+)/s+/1/b。
圆括号构成了1个组合类型,在这个正则表示中它是第1组合类型(也是唯一的1个)。向后扫描/1,指的是任何被/w+所匹配的单词。我们的正则表达式因此能匹配这样的单词,它有1个或多个空格符,后面还跟有1个与此相同的单词。注意的是,尾部的定位类型(/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");
最落后1步的修改是让我们的匹配器对大小写敏感。比如,下面的情况:"The the theme of this article is the Java's regex package.",这1点在regex中能非常简单地实现,即通过使用在Pattern类中预定义的静态标志CASE_INSENSITIVE :
Pattern pattern =Pattern.compile("//b(//w+)//s+//1//b",
Pattern.CASE_INSENSITIVE);
有关正则表达式的话题是非常丰富,而且复杂的,用Java来实现也非常广泛,则需要对regex包进行的完全研究,我们在这里所讲的只是冰山1角。即便你对正则表达式比较陌生,使用regex包后会很快发现它强大功能和可伸缩性。如果你是个来自Perl或其他语言王国的老练的正则表达式的黑客,使用过regex包后,你将会安心肠投入到java的世界,而放弃其他的工具,并把java的regex包看成是手边必备的利器。
<
CharSequence
JDK 1.4定义了1个新的接口,叫CharSequence。它提供了String和StringBuffer这两个类的字符序列的抽象:
interface CharSequence {
charAt(int i);
length();
subSequence(int start, int end);
toString();
}
为了实现这个新的CharSequence接口,String,StringBuffer和CharBuffer都作了修改。很多正则表达式的操作都要拿CharSequence作参数。
Pattern和Matcher
先给1个例子。下面这段程序可以测试正则表达式是不是匹配字符串。第1个参数是要匹配的字符串,后面是正则表达式。正则表达式可以有多个。在Unix/Linux环境下,命令行下的正则表达式还必须用引号。
//: c12:TestRegularExpression.java // Allows you to easly try out regular expressions. // {Args: abcabcabcdefabc "abc+" "(abc)+" "(abc){2,}" } 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( )方法送1个字符串就可以获得1个Matcher对象。另外,Pattern还有1个能快速判断能否在input里面找到regex的
staticboolean matches(?regex, ?input)
和能返回String数组的split( )方法,它能用regex把字符串分割开来。
只要给Pattern.matcher( )方法传1个字符串就可以取得Matcher对象了。接下来就可以用Matcher的方法来查询匹配的结果了。
boolean matches() boolean lookingAt() boolean find() boolean find(int start)
matches( )的条件是Pattern匹配全部字符串,而lookingAt( )的意思是Pattern匹配字符串的开头。
find( )
Matcher.find( )的功能是发现CharSequence里的,与pattern相匹配的多个字符序列。例如:
//: c12:FindDemo.java 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+"的意思是"1个或多个单词字符",因此它会将字符串直接分解成单词。find( )像1个迭代器,从头到尾扫描1遍字符串。第2个find( )是带int参数的,正如你所看到的,它会告知方法从哪里开始找——即从参数位置开始查找。
Groups
Group是指里用括号括起来的,能被后面的表达式调用的正则表达式。Group 0 表示全部表达式,group 1表示第1个被括起来的group,以此类推。所以;
A(B(C))D
里面有3个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个字符的下标加1。
//: c12:Groups.java 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)(//S+)//s+((//S+)//s+(//S+))___FCKpd___6quot;)
.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"的第1部份。可以看到这个正则表达式里有很多用括号括起来的group,它是由任意多个连续的非空字符('/S+')和任意多个连续的空格字符('/s+')所组成的,其终究目的是要捕获每行的最后3个单词;'$'表示1行的结尾。但是'$'通常表示全部字符串的结尾,所以这里要明确地告知正则表达式注意换行符。这1点是由'(?m)'标志完成的(模式标志会过1会讲授)。
start( )和end( )
如果匹配成功,start( )会返回此次匹配的开始位置,end( )会返回此次匹配的结束位置,即最后1个字符的下标加1。如果之前的匹配不成功(或没匹配),那末不管是调用start( )还是end(
),都会引发1个IllegalStateException。下面这段程序还演示了matches( )和lookingAt( ):
//: c12:StartEnd.java 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()) // No reset() 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()) // No reset() necessary 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( ),只有在字符串与正则表达式1开始就相匹配的情况下才能返回true。matches(
)成功的条件是正则表达式与字符串完全匹配,而lookingAt( )成功的条件是,字符串的开始部份与正则表达式相匹配。
匹配的模式(Pattern flags)
compile( )方法还有1个版本,它需要1个控制正则表达式的匹配行动的参数:
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,回车之类)。注释从#开始,1直到这行结束。可以通过嵌入式的标志来启用Unix行模式。
|
Pattern.DOTALL
(?s)
|
在这类模式下,表达式'.'可以匹配任意字符,包括表示1行的结束符。默许情况下,表达式'.'不匹配行的结束符。
|
Pattern.MULTILINE
(?m)
|
在这类模式下,'^'和'$'分别匹配1行的开始和结束。另外,'^'依然匹配字符串的开始,'$'也匹配字符串的结束。默许情况下,这两个表达式仅仅匹配字符串的开始和结束。
|
Pattern.UNICODE_CASE
(?u)
|
在这个模式下,如果你还启用了CASE_INSENSITIVE标志,那末它会对Unicode字符进行大小写不明感的匹配。默许情况下,大小写不明感的匹配只适用于US-ASCII字符集。
|
Pattern.UNIX_LINES
(?d)
|
在这个模式下,只有'/n'才被认作1行的中断,并且与'.','^',和'$'进行匹配。
|
在这些标志里面,Pattern.CASE_INSENSITIVE,Pattern.MULTILINE,和Pattern.COMMENTS是最有用的(其中Pattern.COMMENTS还能帮我们把思路理清楚,并且/或做文档)。注意,你可以用在表达式里插记号的方式来启用绝大多数的模式。这些记号就在上面那张表的各个标志的下面。你希望模式从哪里开始启动,就在哪里插记号。
可以用"OR" ('|')运算符把这些标志合使用:
//: c12:ReFlags.java 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)
这是1种既快又方便地将文本根据1些常见的边界标志分割开来的方法。
//: c12:SplitDemo.java 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))); // Only do the first three: 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!]" });
}
} ///:~
|
第2个split( )会限定分割的次数。
正则表达式是如此重要,以致于有些功能被加进了String类,其中包括split( )(已看到了),matches( ),replaceFirst( )和replaceAll(
)。这些方法的功能同Pattern和Matcher的相同。
替换操作
正则表达式在替换文本方面特别在行。下面就是1些方法:
replaceFirst(String replacement)将字符串里,第1个与模式相匹配的子串替换成replacement。
replaceAll(String replacement),将输入字符串里所有与模式相匹配的子串全部替换成replacement。
appendReplacement(StringBuffer sbuf, String replacement)对sbuf进行逐次替换,而不是像replaceFirst( )或replaceAll(
)那样,只替换第1个或全部子串。这是个非常重要的方法,由于它可以调用方法来生成replacement(replaceFirst( )和replaceAll( )只允许用固定的字符串来充当replacement)。有了这个方法,你就能够编程辨别group,从而实现更强大的替换功能。
调用完appendReplacement( )以后,为了把剩余的字符串拷贝回去,必须调用appendTail(StringBuffer sbuf, String replacement)。
下面我们来演示1下怎样使用这些替换方法。说明1下,这段程序所处理的字符串是它自己开头部份的注释,是用正则表达式提取出来并加以处理以后再传给替换方法的。
//: c12:TheReplacements.java import java.util.regex.*; import java.io.*; import com.bruceeckel.util.*; import com.bruceeckel.simpletest.*; /*! 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. !*/ publicclass TheReplacements { privatestatic Test monitor = new Test(); publicstaticvoid main(String[] args) throws Exception {
String s = TextFile.read("TheReplacements.java"); // Match the specially-commented block of text above: Matcher mInput =
Pattern.compile("///*!(.*)!//*/", Pattern.DOTALL)
.matcher(s); if(mInput.find())
s = mInput.group(1); // Captured by parentheses // Replace two or more spaces with a single space: s = s.replaceAll(" {2,}", " "); // Replace one or more spaces at the beginning of each // line with no spaces. Must enable MULTILINE mode: 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); // Process the find information as you // perform the replacements: while(m.find())
m.appendReplacement(sbuf, m.group().toUpperCase()); // Put in the remainder of the text: 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的功能是匹配'/*!' 和 '!*/' 之间的文本(注意1下分组用的括号)。接下来,我们将所有两个以上的连续空格全都替换成1个,并且将各行开头的空格全都去掉(为了让这个正则表达式能对所有的行,而不单单是第1行起作用,必须启用多行模式)。这两个操作都用了String的replaceAll(
)(这里用它更方便)。注意,由于每一个替换只做1次,因此除预编译
|