在Sun的Java JDK 1.40版本中,Java自带了支持正则表达式的包,本文就抛砖引玉地介绍了如何使用java.util.regex包。 java
可粗略估计一下,除了偶尔用Linux的外,其余Linu x用户都会遇到正则表达式。正则表达式是个极端强大工具,并且在字符串模式-匹配和字符串模式-替换方面富有弹性。在Unix世界里,正则表达式几乎没有什么限制,可确定的是,它应用很是之普遍。 正则表达式
正则表达式的引擎已被许多普通的Unix工具所实现,包括grep,awk,vi和Emacs等。此外,许多使用比较普遍的脚本语言也支持正则表达式,好比Python,Tcl,JavaScript,以及最著名的Perl。 spring
我很早之前就是个Perl方面的***,若是你和我同样话,你也会很是依赖你手边的这些强大的text-munging工具。近几年来,像其余程序开发者同样,我也愈来愈关注Java的开发。 express
Java做为一种开发语言,有许多值得推荐的地方,可是它一直以来没有自带对正则表达式的支持。直到最近,借助于第三方的类库,Java开始支持正则表达式,但这些第三方的类库都不一致、兼容性差,并且维护代码起来很糟糕。这个缺点,对我选择Java做为首要的开发工具来讲,一直是个巨大的顾虑之处。 编程
你能够想象,当我知道Sun的Java JDK 1.40版本包含了java.util.regex(一个彻底开放、自带的正则表达式包)时,是多么的高兴!很搞笑的说,我花好些时间去挖掘这个被隐藏起来的宝石。我很是惊奇的是,Java这样的一个很大改进(自带了java.util.regex包)为何很少公开一点呢?! 数组
最近,Java双脚都跳进了正则表达式的世界。java.util.regex包在支持正则表达也有它的过人之处,另外Java也提供详细的相关说明文档。使得朦朦胧胧的regex神秘景象也慢慢被拨开。有一些正则表达式的构成(可能最显著的是,在于糅合了字符类库)在Perl都找不到。 app
在regex包中,包括了两个类,Pattern(模式类)和Matcher(匹配器类)。Pattern类是用来表达和陈述所要搜索模式的对象,Matcher类是真正影响搜索的对象。另加一个新的例外类,PatternSyntaxException,当遇到不合法的搜索模式时,会抛出例外。 ide
即便对正则表达式很熟悉,你会发现,经过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.*;
下面的一段代码实现的功能是,从一个文本文件逐行读入,并逐行搜索电话号码数字,一旦找到所匹配的,而后输出在控制台。
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这两个类的字符序列的抽象:
interface CharSequence { charAt(int i); length(); subSequence(int start, int end); toString(); }
为了实现这个新的CharSequence接口,String,StringBuffer以及CharBuffer都做了修改。不少正则表达式的操做都要拿CharSequence做参数。
Pattern和Matcher
先给一个例子。下面这段程序能够测试正则表达式是否匹配字符串。第一个参数是要匹配的字符串,后面是正则表达式。正则表达式能够有多个。在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( )方法送一个字符串就能获取一个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相匹配的多个字符序列。例如:
//: 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+"的意思是"一个或多个单词字符",所以它会将字符串直接分解成单词。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的结束位置,最后一个字符的下标加一。
//: 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"的第一部分。能够看到这个正则表达式里有不少用括号括起来的group,它是由任意多个连续的非空字符('/S+')和任意多个连续的空格字符('/s+')所组成的,其最终目的是要捕获每行的最后三个单词;'$'表示一行的结尾。可是'$'一般表示整个字符串的结尾,因此这里要明确地告诉正则表达式注意换行符。这一点是由'(?m)'标志完成的(模式标志会过一会讲解)。
start( )和end( )
若是匹配成功,start( )会返回这次匹配的开始位置,end( )会返回这次匹配的结束位置,即最后一个字符的下标加一。若是以前的匹配不成功(或者没匹配),那么不管是调用start( )仍是end( ),都会引起一个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( ),只有在字符串与正则表达式一开始就相匹配的状况下才能返回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
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
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!]" }); } } ///:~
第二个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)。
下面咱们来演示一下怎样使用这些替换方法。说明一下,这段程序所处理的字符串是它本身开头部分的注释,是用正则表达式提取出来并加以处理以后再传给替换方法的。
//: 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的功能是匹配'/*!' 和 '!*/' 之间的文本(注意一下分组用的括号)。接下来,咱们将全部两个以上的连续空格全都替换成一个,而且将各行开头的空格全都去掉(为了让这个正则表达式能对全部的行,而不只仅是第一行起做用,必须启用多行模式)。这两个操做都用了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。
//: c12:Resetting.java 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设到当前字符串的开始处。