1. 程式人生 > 程式設計 >JAVA 正則表示式陳廣佳版本(超詳細)

JAVA 正則表示式陳廣佳版本(超詳細)

在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.*;

  下面的一段程式碼實現的功能是,從一個文字檔案逐行讀入,並逐行搜尋電話號碼數字,一旦找到所匹配的,然後輸出在控制檯。

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這兩個類的字元序列的抽象:

 CharSequence {
 charAt( i);
 length();
 subSequence( start,end);
 toString();
}

為了實現這個新的CharSequence介面,String,StringBuffer以及CharBuffer都作了修改。很多正則表示式的操作都要拿CharSequence作引數。

Pattern和Matcher

先給一個例子。下面這段程式可以測試正則表示式是否匹配字串。第一個引數是要匹配的字串,後面是正則表示式。正則表示式可以有多個。在Unix/Linux環境下,命令列下的正則表示式還必須用引號。

 java.util.regex.*;
 TestRegularExpression {
 main(String[] args) {
(args.length < 2) {
 System.out.println( +
 +
);
 System.exit(0);
 }
 System.out.println(/);
( i = 1; i < args.length; i++) {
 System.out.println(
/);
 Pattern p = Pattern.compile(args[i]);
 Matcher m = p.matcher(args[0]);
(m.find()) {
 System.out.println(" + m.group() +
 at positions " +
  m.start() + + (m.end() - 1));
 }
 }
 }
}

Java的正則表示式是由java.util.regex的Pattern和Matcher類實現的。Pattern物件表示經編譯的正則表示式。靜態的compile( )方法負責將表示正則表示式的字串編譯成Pattern物件。正如上述例程所示的,只要給Pattern的matcher( )方法送一個字串就能獲取一個Matcher物件。此外,Pattern還有一個能快速判斷能否在input裡面找到regex的

matches(?regex,?input)

以及能返回String陣列的split( )方法,它能用regex把字串分割開來。

只要給Pattern.matcher( )方法傳一個字串就能獲得Matcher物件了。接下來就能用Matcher的方法來查詢匹配的結果了。

matches()
lookingAt()
find()
find( start)

matches( )的前提是Pattern匹配整個字串,而lookingAt( )的意思是Pattern匹配字串的開頭。

find( )

Matcher.find( )的功能是發現CharSequence裡的,與pattern相匹配的多個字元序列。例如:

 java.util.regex.*;
 com.bruceeckel.simpletest.*;
 java.util.*;
 FindDemo {
 Test monitor = Test();
 main(String[] args) {
 Matcher m = Pattern.compile()
 .matcher();
(m.find())
 System.out.println(m.group());
 i = 0;
(m.find(i)) {
 System.out.print(m.group() + );
 i++;
 }
 monitor.expect( String[] {,+
 +

 });
 }
} 

"//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的結束位置,最後一個字元的下標加一。

java.util.regex.*;
 com.bruceeckel.simpletest.*;
 Groups {
 Test monitor = Test();
 String poem =
 +
 +
 +
 +
 +
 +
 +
;
 main(String[] args) {
 Matcher m =
 Pattern.compile()
 .matcher(poem);
(m.find()) {
( j = 0; j <= m.groupCount(); j++)
 System.out.print( + m.group(j) + );
 System.out.println();
 }
 monitor.expect( String[]{
 +,+,+

 });
 }
}

這首詩是Through the Looking Glass的,Lewis Carroll的"Jabberwocky"的第一部分。可以看到這個正則表示式裡有很多用括號括起來的group,它是由任意多個連續的非空字元('/S+')和任意多個連續的空格字元('/s+')所組成的,其最終目的是要捕獲每行的最後三個單詞;'$'表示一行的結尾。但是'$'通常表示整個字串的結尾,所以這裡要明確地告訴正則表示式注意換行符。這一點是由'(?m)'標誌完成的(模式標誌會過一會講解)。

start( )和end( )

如果匹配成功,start( )會返回此次匹配的開始位置,end( )會返回此次匹配的結束位置,即最後一個字元的下標加一。如果之前的匹配不成功(或者沒匹配),那麼無論是呼叫start( )還是end( ),都會引發一個IllegalStateException。下面這段程式還演示了matches( )和lookingAt( ):

java.util.regex.*;
 com.bruceeckel.simpletest.*;
 StartEnd {
 Test monitor = Test();
 main(String[] args) {
 String[] input = String[] {,};
 Pattern
 p1 = Pattern.compile(),p2 = Pattern.compile();
( i = 0; i < input.length; i++) {
 System.out.println( + i + + input[i]);
 Matcher
 m1 = p1.matcher(input[i]),m2 = p2.matcher(input[i]);
(m1.find())
 System.out.println( + m1.group() +
+ m1.start() + + m1.end());
(m2.find())
 System.out.println( + m2.group() +
+ m2.start() + + m2.end());
(m1.lookingAt()) 
 System.out.println(
  + m1.start() + + m1.end());
(m2.lookingAt())
 System.out.println(
  + m2.start() + + m2.end());
(m1.matches()) 
 System.out.println(
  + m1.start() + + m1.end());
(m2.matches())
 System.out.println(
  + m2.start() + + m2.end());
 }
 monitor.expect( String[] {,});
 }
}

注意,只要字串裡有這個模式,find( )就能把它給找出來,但是lookingAt( )和matches( ),只有在字串與正則表示式一開始就相匹配的情況下才能返回true。matches( )成功的前提是正則表示式與字串完全匹配,而lookingAt( )成功的前提是,字串的開始部分與正則表示式相匹配。

匹配的模式(Pattern flags)

compile( )方法還有一個版本,它需要一個控制正則表示式的匹配行為的引數:

Pattern Pattern.compile(String regex,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" ('|')運算子把這些標誌合使用:

java.util.regex.*;
 com.bruceeckel.simpletest.*;
 ReFlags {
 Test monitor = Test();
 main(String[] args) {
 Pattern p = Pattern.compile(,Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
 Matcher m = p.matcher(
 +
 +
);
(m.find())
 System.out.println(m.group());
 monitor.expect( String[] {,});
 }
}

這樣創建出來的正則表示式就能匹配以"java","Java","JAVA"...開頭的字串了。此外,如果字串分好幾行,那它還會對每一行做匹配(匹配始於字元序列的開始,終於字元序列當中的行結束符)。注意,group( )方法僅返回匹配的部分。

split( )

所謂分割是指將以正則表示式為界,將字串分割成String陣列。

String[] split(CharSequence charseq)
String[] split(CharSequence charseq,limit)

這是一種既快又方便地將文字根據一些常見的邊界標誌分割開來的方法。

java.util.regex.*;
 com.bruceeckel.simpletest.*;
 java.util.*;
 SplitDemo {
 Test monitor = Test();
 main(String[] args) {
 String input =
;
 System.out.println(Arrays.asList(
 Pattern.compile().split(input)));

 System.out.println(Arrays.asList(
 Pattern.compile().split(input,3)));
 System.out.println(Arrays.asList(
.split()));
 monitor.expect( String[] {,});
 }
} 

第二個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)。

下面我們來演示一下怎樣使用這些替換方法。說明一下,這段程式所處理的字串是它自己開頭部分的註釋,是用正則表示式提取出來並加以處理之後再傳給替換方法的。

java.util.regex.*;
 java.io.*;
 com.bruceeckel.util.*;
 com.bruceeckel.simpletest.*;

 TheReplacements {
 Test monitor = Test();
 main(String[] args) Exception {
 String s = TextFile.read();

 Matcher mInput =
 Pattern.compile(,Pattern.DOTALL)
 .matcher(s);
(mInput.find())
 s = mInput.group(1); 

 s = s.replaceAll(,);


 s = s.replaceAll(,);
 System.out.println(s);
 s = s.replaceFirst(,);
 StringBuffer sbuf = StringBuffer();
 Pattern p = Pattern.compile();
 Matcher m = p.matcher(s);


(m.find())
 m.appendReplacement(sbuf,m.group().toUpperCase());

 m.appendTail(sbuf);
 System.out.println(sbuf);
 monitor.expect( String[]{,});
 }
} 

用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。

java.util.regex.*;
 java.io.*;
 com.bruceeckel.simpletest.*;
 Resetting {
 Test monitor = Test();
 main(String[] args) Exception {
 Matcher m = Pattern.compile()
 .matcher();
(m.find())
 System.out.println(m.group());
 m.reset();
(m.find())
 System.out.println(m.group());
 monitor.expect( String[]{,});
 }
} 

如果不給引數,reset( )會把Matcher設到當前字串的開始處。

如果你曾經用過Perl或任何其他內建正則表示式支援的語言,你一定知道用正則表示式處理文字和匹配模式是多麼簡單。如果你不熟悉這個術語,那麼“正則表示式”(Regular Expression)就是一個字元構成的串,它定義了一個用來搜尋匹配字串的模式。

許多語言,包括Perl、PHP、Python、JavaScript和JScript,都支援用正則表示式處理文字,一些文字編輯器用正則表示式實現高階“搜尋-替換”功能。那麼Java又怎樣呢?本文寫作時,一個包含了用正則表示式進行文字處理的Java規範需求(Specification Request)已經得到認可,你可以期待在JDK的下一版本中看到它。

然而,如果現在就需要使用正則表示式,又該怎麼辦呢?你可以從Apache.org下載原始碼開放的Jakarta-ORO庫。本文接下來的內容先簡要地介紹正則表示式的入門知識,然後以Jakarta-ORO API為例介紹如何使用正則表示式。

一、正則表示式基礎知識

我們先從簡單的開始。假設你要搜尋一個包含字元“cat”的字串,搜尋用的正則表示式就是“cat”。如果搜尋對大小寫不敏感,單詞“catalog”、“Catherine”、“sophisticated”都可以匹配。也就是說:

JAVA 正則表示式陳廣佳版本(超詳細)

1.1 句點符號
假設你在玩英文拼字遊戲,想要找出三個字母的單詞,而且這些單詞必須以“t”字母開頭,以“n”字母結束。另外,假設有一本英文字典,你可以用正則表示式搜尋它的全部內容。要構造出這個正則表示式,你可以使用一個萬用字元——句點符號“.”。這樣,完整的表示式就是“t.n”,它匹配“tan”、“ten”、“tin”和“ton”,還匹配“t#n”、“tpn”甚至“t n”,還有其他許多無意義的組合。這是因為句點符號匹配所有字元,包括空格、Tab字元甚至換行符:

JAVA 正則表示式陳廣佳版本(超詳細)

1.2 方括號符號

為了解決句點符號匹配範圍過於廣泛這一問題,你可以在方括號(“[]”)裡面指定看來有意義的字元。此時,只有方括號裡面指定的字元才參與匹配。也就是說,正則表示式“t[aeio]n”只匹配“tan”、“Ten”、“tin”和“ton”。但“Toon”不匹配,因為在方括號之內你只能匹配單個字元:

JAVA 正則表示式陳廣佳版本(超詳細)

1.3 “或”符號
如果除了上面匹配的所有單詞之外,你還想要匹配“toon”,那麼,你可以使用“|”操作符。“|”操作符的基本意義就是“或”運算。要匹配“toon”,使用“t(a|e|i|o|oo)n”正則表示式。這裡不能使用方擴號,因為方括號只允許匹配單個字元;這裡必須使用圓括號“()”。圓括號還可以用來分組,具體請參見後面介紹。

JAVA 正則表示式陳廣佳版本(超詳細)

1.4 表示匹配次數的符號
表一顯示了表示匹配次數的符號,這些符號用來確定緊靠該符號左邊的符號出現的次數:

JAVA 正則表示式陳廣佳版本(超詳細)

假設我們要在文字檔案中搜索美國的社會安全號碼。這個號碼的格式是999-99-9999。用來匹配它的正則表示式如圖一所示。在正則表示式中,連字元(“-”)有著特殊的意義,它表示一個範圍,比如從0到9。因此,匹配社會安全號碼中的連字元號時,它的前面要加上一個轉義字元“/”。

JAVA 正則表示式陳廣佳版本(超詳細)

圖一:匹配所有123-12-1234形式的社會安全號碼

假設進行搜尋的時候,你希望連字元號可以出現,也可以不出現——即,999-99-9999和999999999都屬於正確的格式。這時,你可以在連字元號後面加上“?”數量限定符號,如圖二所示:

JAVA 正則表示式陳廣佳版本(超詳細)

圖二:匹配所有123-12-1234和123121234形式的社會安全號碼

下面我們再來看另外一個例子。美國汽車牌照的一種格式是四個數字加上二個字母。它的正則表示式前面是數字部分“[0-9]{4}”,再加上字母部分“[A-Z]{2}”。圖三顯示了完整的正則表示式。

JAVA 正則表示式陳廣佳版本(超詳細)

圖三:匹配典型的美國汽車牌照號碼,如8836KV

1.5 “否”符號
“^”符號稱為“否”符號。如果用在方括號內,“^”表示不想要匹配的字元。例如,圖四的正則表示式匹配所有單詞,但以“X”字母開頭的單詞除外。

JAVA 正則表示式陳廣佳版本(超詳細)

圖四:匹配所有單詞,但“X”開頭的除外

1.6 圓括號和空白符號
假設要從格式為“June 26,1951”的生日日期中提取出月份部分,用來匹配該日期的正則表示式可以如圖五所示:

JAVA 正則表示式陳廣佳版本(超詳細)

圖五:匹配所有Moth DD,YYYY格式的日期

新出現的“/s”符號是空白符號,匹配所有的空白字元,包括Tab字元。如果字串正確匹配,接下來如何提取出月份部分呢?只需在月份周圍加上一個圓括號建立一個組,然後用ORO API(本文後面詳細討論)提取出它的值。修改後的正則表示式如圖六所示:

JAVA 正則表示式陳廣佳版本(超詳細)

圖六:匹配所有Month DD,YYYY格式的日期,定義月份值為第一個組

1.7 其它符號

為簡便起見,你可以使用一些為常見正則表示式建立的快捷符號。如表二所示:
表二:常用符號

JAVA 正則表示式陳廣佳版本(超詳細)

例如,在前面社會安全號碼的例子中,所有出現“[0-9]”的地方我們都可以使用“/d”。修改後的正則表示式如圖七所示:

JAVA 正則表示式陳廣佳版本(超詳細)

圖七:匹配所有123-12-1234格式的社會安全號碼

二、Jakarta-ORO庫

有許多原始碼開放的正則表示式庫可供Java程式設計師使用,而且它們中的許多支援Perl 5相容的正則表示式語法。我在這裡選用的是Jakarta-ORO正則表示式庫,它是最全面的正則表示式API之一,而且它與Perl 5正則表示式完全相容。另外,它也是優化得最好的API之一。

Jakarta-ORO庫以前叫做OROMatcher,Daniel Savarese大方地把它贈送給了Jakarta Project。你可以按照本文最後參考資源的說明下載它。

我首先將簡要介紹使用Jakarta-ORO庫時你必須建立和訪問的物件,然後介紹如何使用Jakarta-ORO API。

▲ PatternCompiler物件

首先,建立一個Perl5Compiler類的例項,並把它賦值給PatternCompiler介面物件。Perl5Compiler是PatternCompiler介面的一個實現,允許你把正則表示式編譯成用來匹配的Pattern物件。

JAVA 正則表示式陳廣佳版本(超詳細)

▲ Pattern物件

要把正則表示式編譯成Pattern物件,呼叫compiler物件的compile()方法,並在呼叫引數中指定正則表示式。例如,你可以按照下面這種方式編譯正則表示式“t[aeio]n”:

JAVA 正則表示式陳廣佳版本(超詳細)

預設情況下,編譯器建立一個大小寫敏感的模式(pattern)。因此,上面程式碼編譯得到的模式只匹配“tin”、“tan”、 “ten”和“ton”,但不匹配“Tin”和“taN”。要建立一個大小寫不敏感的模式,你應該在呼叫編譯器的時候指定一個額外的引數:

JAVA 正則表示式陳廣佳版本(超詳細)

建立好Pattern物件之後,你就可以通過PatternMatcher類用該Pattern物件進行模式匹配。

▲ PatternMatcher物件

PatternMatcher物件根據Pattern物件和字串進行匹配檢查。你要例項化一個Perl5Matcher類並把結果賦值給PatternMatcher介面。Perl5Matcher類是PatternMatcher介面的一個實現,它根據Perl 5正則表示式語法進行模式匹配:

JAVA 正則表示式陳廣佳版本(超詳細)

使用PatternMatcher物件,你可以用多個方法進行匹配操作,這些方法的第一個引數都是需要根據正則表示式進行匹配的字串:

· boolean matches(String input,Pattern pattern):當輸入字串和正則表示式要精確匹配時使用。換句話說,正則表示式必須完整地描述輸入字串。
· boolean matchesPrefix(String input,Pattern pattern):當正則表示式匹配輸入字串起始部分時使用。
· boolean contains(String input,Pattern pattern):當正則表示式要匹配輸入字串的一部分時使用(即,它必須是一個子串)。
另外,在上面三個方法呼叫中,你還可以用PatternMatcherInput物件作為引數替代String物件;這時,你可以從字串中最後一次匹配的位置開始繼續進行匹配。當字串可能有多個子串匹配給定的正則表示式時,用PatternMatcherInput物件作為引數就很有用了。用PatternMatcherInput物件作為引數替代String時,上述三個方法的語法如下:

· boolean matches(PatternMatcherInput input,Pattern pattern)
· boolean matchesPrefix(PatternMatcherInput input,Pattern pattern)
· boolean contains(PatternMatcherInput input,Pattern pattern)

三、應用例項

下面我們來看看Jakarta-ORO庫的一些應用例項。

3.1 日誌檔案處理

任務:分析一個Web伺服器日誌檔案,確定每一個使用者花在網站上的時間。在典型的BEA WebLogic日誌檔案中,日誌記錄的格式如下:

JAVA 正則表示式陳廣佳版本(超詳細)

分析這個日誌記錄,可以發現,要從這個日誌檔案提取的內容有兩項:IP地址和頁面訪問時間。你可以用分組符號(圓括號)從日誌記錄提取出IP地址和時間標記。

首先我們來看看IP地址。IP地址有4個位元組構成,每一個位元組的值在0到255之間,各個位元組通過一個句點分隔。因此,IP地址中的每一個位元組有至少一個、最多三個數字。圖八顯示了為IP地址編寫的正則表示式:

JAVA 正則表示式陳廣佳版本(超詳細)

圖八:匹配IP地址

IP地址中的句點字元必須進行轉義處理(前面加上“/”),因為IP地址中的句點具有它本來的含義,而不是採用正則表示式語法中的特殊含義。句點在正則表示式中的特殊含義本文前面已經介紹。
日誌記錄的時間部分由一對方括號包圍。你可以按照如下思路提取出方括號裡面的所有內容:首先搜尋起始方括號字元(“[”),提取出所有不超過結束方括號字元(“]”)的內容,向前尋找直至找到結束方括號字元。圖九顯示了這部分的正則表示式。

JAVA 正則表示式陳廣佳版本(超詳細)

圖九:匹配至少一個字元,直至找到“]”

現在,把上述兩個正則表示式加上分組符號(圓括號)後合併成單個表示式,這樣就可以從日誌記錄提取出IP地址和時間。注意,為了匹配“- -”(但不提取它),正則表示式中間加入了“/s-/s-/s”。完整的正則表示式如圖十所示。

JAVA 正則表示式陳廣佳版本(超詳細)

圖十:匹配IP地址和時間標記

現在正則表示式已經編寫完畢,接下來可以編寫使用正則表示式庫的Java程式碼了。
為使用Jakarta-ORO庫,首先建立正則表示式字串和待分析的日誌記錄字串:

JAVA 正則表示式陳廣佳版本(超詳細)

這裡使用的正則表示式與圖十的正則表示式差不多完全相同,但有一點例外:在Java中,你必須對每一個向前的斜槓(“/”)進行轉義處理。圖十不是Java的表示形式,所以我們要在每個“/”前面加上一個“/”以免出現編譯錯誤。遺憾的是,轉義處理過程很容易出現錯誤,所以應該小心謹慎。你可以首先輸入未經轉義處理的正則表示式,然後從左到右依次把每一個“/”替換成“//”。如果要複檢,你可以試著把它輸出到螢幕上。

初始化字串之後,例項化PatternCompiler物件,用PatternCompiler編譯正則表示式建立一個Pattern物件:

JAVA 正則表示式陳廣佳版本(超詳細)

現在,建立PatternMatcher物件,呼叫PatternMatcher介面的contain()方法檢查匹配情況:

JAVA 正則表示式陳廣佳版本(超詳細)

接下來,利用PatternMatcher介面返回的MatchResult物件,輸出匹配的組。由於logEntry字串包含匹配的內容,你可以看到類如下面的輸出:

JAVA 正則表示式陳廣佳版本(超詳細)

3.2 HTML處理例項一

下面一個任務是分析HTML頁面內FONT標記的所有屬性。HTML頁面內典型的FONT標記如下所示:

JAVA 正則表示式陳廣佳版本(超詳細)

程式將按照如下形式,輸出每一個FONT標記的屬性:

JAVA 正則表示式陳廣佳版本(超詳細)

在這種情況下,我建議你使用兩個正則表示式。第一個如圖十一所示,它從字型標記提取出“"face="Arial,Serif" size="+2" color="red"”。

JAVA 正則表示式陳廣佳版本(超詳細)

圖十一:匹配FONT標記的所有屬性

第二個正則表示式如圖十二所示,它把各個屬性分割成名字-值對。

JAVA 正則表示式陳廣佳版本(超詳細)

圖十二:匹配單個屬性,並把它分割成名字-值對

分割結果為:

JAVA 正則表示式陳廣佳版本(超詳細)

現在我們來看看完成這個任務的Java程式碼。首先建立兩個正則表示式字串,用Perl5Compiler把它們編譯成Pattern物件。編譯正則表示式的時候,指定Perl5Compiler.CASE_INSENSITIVE_MASK選項,使得匹配操作不區分大小寫。
接下來,建立一個執行匹配操作的Perl5Matcher物件。

JAVA 正則表示式陳廣佳版本(超詳細)

假設有一個String型別的變數html,它代表了HTML檔案中的一行內容。如果html字串包含FONT標記,匹配器將返回true。此時,你可以用匹配器物件返回的MatchResult物件獲得第一個組,它包含了FONT的所有屬性:

JAVA 正則表示式陳廣佳版本(超詳細)

接下來建立一個PatternMatcherInput物件。這個物件允許你從最後一次匹配的位置開始繼續進行匹配操作,因此,它很適合於提取FONT標記內屬性的名字-值對。建立PatternMatcherInput物件,以引數形式傳入待匹配的字串。然後,用匹配器例項提取出每一個FONT的屬性。這通過指定PatternMatcherInput物件(而不是字串物件)為引數,反覆地呼叫PatternMatcher物件的contains()方法完成。PatternMatcherInput物件之中的每一次迭代將把它內部的指標向前移動,下一次檢測將從前一次匹配位置的後面開始。

本例的輸出結果如下:

JAVA 正則表示式陳廣佳版本(超詳細)

3.3 HTML處理例項二

下面我們來看看另一個處理HTML的例子。這一次,我們假定Web伺服器從widgets.acme.com移到了newserver.acme.com。現在你要修改一些頁面中的連結:

JAVA 正則表示式陳廣佳版本(超詳細)

執行這個搜尋的正則表示式如圖十三所示:

JAVA 正則表示式陳廣佳版本(超詳細)

圖十三:匹配修改前的連結

如果能夠匹配這個正則表示式,你可以用下面的內容替換圖十三的連結:

JAVA 正則表示式陳廣佳版本(超詳細)

注意#字元的後面加上了$1。Perl正則表示式語法用$1、$2等表示已經匹配且提取出來的組。圖十三的表示式把所有作為一個組匹配和提取出來的內容附加到連結的後面。
現在,返回Java。就象前面我們所做的那樣,你必須建立測試字串,建立把正則表示式編譯到Pattern物件所必需的物件,以及建立一個PatternMatcher對像

JAVA 正則表示式陳廣佳版本(超詳細)

接下來,用com.oroinc.text.regex包Util類的substitute()靜態方法進行替換,輸出結果字串:

JAVA 正則表示式陳廣佳版本(超詳細)

Util.substitute()方法的語法如下:

JAVA 正則表示式陳廣佳版本(超詳細)

這個呼叫的前兩個引數是以前建立的PatternMatcher和Pattern物件。第三個引數是一個Substiution物件,它決定了替換操作如何進行。本例使用的是Perl5Substitution物件,它能夠進行Perl5風格的替換。第四個引數是想要進行替換操作的字串,最後一個引數允許指定是否替換模式的所有匹配子串(Util.SUBSTITUTE_ALL),或只替換指定的次數。

【結束語】在這篇文章中,我為你介紹了正則表示式的強大功能。只要正確運用,正則表示式能夠在字串提取和文字修改中起到很大的作用。另外,我還介紹瞭如何在Java程式中通過Jakarta-ORO庫利用正則表示式。至於最終採用老式的字串處理方式(使用StringTokenizer,charAt,和substring),還是採用正則表示式,這就有待你自己決定了。

Jakarta-ORO篇

由於工作的需要,本人經常要面對大量的文字電子資料的整理工作,因此曾對在JAVA中正則表示式的應用有所關注,並對其有一定的瞭解,希望通過本文與同行進行有關方面的心得交流。

正則表示式:
正則表示式是一種可以用於模式匹配和替換的強有力的工具,一個正則表示式就是由普通的字元(例如字元 a 到 z)以及特殊字元(稱為元字元)組成的文字模式,它描述在查詢文字主體時待匹配的一個或多個字串。正則表示式作為一個模板,將某個字元模式與所搜尋的字串進行匹配。

正則表示式在字元資料處理中起著非常重要的作用,我們可以用正則表示式完成大部分的資料分析處理工作,如:判斷一個串是否是數字、是否是有效的Email地址,從海量的文字資料中提取有價值的資料等等,如果不使用正則表示式,那麼實現的程式可能會很長,並且容易出錯。對這點本人深有體會,面對大量工具書電子檔資料的整理工作,如果不懂得應用正則表示式來處理,那麼將是很痛苦的一件事情,反之則將可以輕鬆地完成,獲得事半功倍的效果。

由於本文目的是要介紹如何在JAVA裡運用正則表示式,因此對剛接觸正則表示式的讀者請參考有關資料,在此因篇幅有限不作介紹。

JAVA對正則表示式的支援:
在JDK1.3或之前的JDK版本中並沒有包含正則表示式庫可供JAVA程式設計師使用,之前我們一般都在使用第三方提供的正則表示式庫,這些第三方庫中有原始碼開放的,也有需付費購買的,而現時在JDK1.4的測試版中也已經包含有正則表示式庫---java.util.regex。

故此現在我們有很多面向JAVA的正則表示式庫可供選擇,以下我將介紹兩個較具代表性的 Jakarta-ORO和java.util.regex,首先當然是本人一直在用的 Jakarta-ORO:

Jakarta-ORO正則表示式庫

1.簡介:

Jakarta-ORO是最全面以及優化得最好的正則表示式API之一,Jakarta-ORO庫以前叫做OROMatcher,是由Daniel F. Savarese編寫,後來他將其贈與Jakarta Project,讀者可在Apache.org的網站下載該API包。

許多原始碼開放的正則表示式庫都是支援Perl5相容的正則表示式語法,Jakarta-ORO正則表示式庫也不例外,他與Perl 5正則表示式完全相容。

2.物件與其方法:

★PatternCompiler物件:
我們在使用Jakarta-ORO API包時,最先要做的是,建立一個Perl5Compiler類的例項,並把它賦值給PatternCompiler介面物件。Perl5Compiler是PatternCompiler介面的一個實現,允許你把正則表示式編譯成用來匹配的Pattern物件。

PatternCompiler compiler=new Perl5Compiler();

★Pattern物件:
要把所對應的正則表示式編譯成Pattern物件,需要呼叫compiler物件的compile()方法,並在呼叫引數中指定正則表示式。舉個例子,你可以按照下面這種方式編譯正則表示式"s[ahkl]y":

Pattern pattern=null;
try {
pattern=compiler.compile("s[ahkl]y ");
} catch (MalformedPatternException e) {
e.printStackTrace();
}

在預設的情況下,編譯器會建立一個對大小寫敏感的模式(pattern)。因此,上面程式碼編譯得到的模式只匹配"say"、"shy"、 "sky"和"sly",但不匹配"Say"和"skY"。要建立一個大小寫不敏感的模式,你應該在呼叫編譯器的時候指定一個額外的引數:

pattern=compiler.compile("s[ahkl]y",Perl5Compiler.CASE_INSENSITIVE_MASK);

Pattern物件建立好之後,就可以通過PatternMatcher類用該Pattern物件進行模式匹配。

★PatternMatcher物件:

PatternMatcher物件依據Pattern物件和字串展開匹配檢查。你要例項化一個Perl5Matcher類並把結果賦值給PatternMatcher介面。Perl5Matcher類是PatternMatcher介面的一個實現,它根據Perl 5正則表示式語法進行模式匹配:
PatternMatcher matcher=new Perl5Matcher();

PatternMatcher物件提供了多個方法進行匹配操作,這些方法的第一個引數都是需要根據正則表示式進行匹配的字串:

1、boolean matches(String input,Pattern pattern):當要求輸入的字串input和正則表示式pattern精確匹配時使用該方法。也就是說當正則表示式完整地描述輸入字串時返回真值。
2、boolean matchesPrefix(String input,Pattern pattern):要求正則表示式匹配輸入字串起始部分時使用該方法。也就是說當輸入字串的起始部分與正則表示式匹配時返回真值。
3、boolean contains(String input,Pattern pattern):當正則表示式要匹配輸入字串的一部分時使用該方法。當正則表示式為輸入字串的子串時返回真值。

但以上三種方法只會查詢輸入字串中匹配正則表示式的第一個物件,如果當字串可能有多個子串匹配給定的正則表示式時,那麼你就可以在呼叫上面三個方法時用PatternMatcherInput物件作為引數替代String物件,這樣就可以從字串中最後一次匹配的位置開始繼續進行匹配,這樣就方便的多了。

用PatternMatcherInput物件作為引數替代String時,上述三個方法的語法如下:

  • boolean matches(PatternMatcherInput input,Pattern pattern)
  • boolean matchesPrefix(PatternMatcherInput input,Pattern pattern)
  • boolean contains(PatternMatcherInput input,Pattern pattern)

★Util.substitute()方法:
查詢後需要要進行替換,我們就要用到Util.substitute()方法,其語法如下:

public static String substitute(PatternMatcher matcher,
Pattern pattern,Substitution sub,String input,
int numSubs)

前兩個引數分別為PatternMatcher和Pattern物件。而第三個引數是個Substiution物件,由它來決定替換操作如何進行。第四個引數是要進行替換操作的目標字串,最後一個引數用來指定是否替換模式的所有匹配子串(Util.SUBSTITUTE_ALL),或只進行指定次數的替換。

在這裡我相信有必要詳細解說一下第三個引數Substiution物件,因為它將決定替換將怎樣進行。

Substiution:
Substiution是一個介面類,它為你提供了在使用Util.substitute()方法時控制替換方式的手段,它有兩個標準的實現類:StringSubstitution與Perl5Substitution。當然,同時你也可以生成自己的實現類來定製你所需要的特殊替換動作。

StringSubstitution:
StringSubstitution 實現的是簡單的純文字替換手段,它有兩個構造方法:

StringSubstitution()->預設的構造方法,初始化一個包含零長度字串的替換物件。

StringSubstitution(java.lang.String substitution)->初始化一個給定字串的替換物件。

Perl5Substitution:
Perl5Substitution 是StringSubstitution的子類,它在實現純文字替換手段的同時也允許進行鍼對MATH類裡各匹配組的PERL5變數的替換,所以他的替換手段比其直接父類StringSubstitution更為多元化。

它有三個構造器:

Perl5Substitution()

Perl5Substitution(java.lang.String substitution)

Perl5Substitution(java.lang.String substitution,int numInterpolations)

前兩種構造方法與StringSubstitution一樣,而第三種構造方法下面將會介紹到。

在Perl5Substitution的替換字串中可以包含用來替代在正則表示式裡由小擴號圍起來的匹配組的變數,這些變數是由$1,$2,$3等形式來標識。我們可以用一個例子來解釋怎樣使用替換變數來進行替換:

假設我們有正則表示式模式為b/d+:(也就是b[0-9]+:),而我們想把所有匹配的字串中的"b"都改為"a",而":"則改為"-",而其餘部分則不作修改,如我們輸入字串為"EXAMPLE b123:",經過替換後就應該變成"EXAMPLE a123-"。要做到這點,我們就首先要把不做替換的部分用分組符號小括號包起來,這樣正則表示式就變為"b(/d+):",而構造Perl5Substitution物件時其替換字串就應該是"a$1-",也就是構造式為Perl5Substitution("a$1-"),表示在使用Util.substitute()方法時只要在目標字串裡找到和正則表示式" b(/d+): "相匹配的子串都用替換字串來替換,而變數$1表示如果和正則表示式裡第一個組相匹配的內容則照般原文插到$1所在的為置,如在"EXAMPLE b123:"中和正則表示式相匹配的部分是"b123:",而其中和第一分組"(/d+)"相匹配的部分則是"123",所以最後替換結果為"EXAMPLE a123-"。

有一點需要清楚的是,如果你把構造器Perl5Substitution(java.lang.String substitution,int numInterpolations)

中的numInterpolations引數設為INTERPOLATE_ALL,那麼當每次找到一個匹配字串時,替換變數($1,$2等)所指向的內容都根據目前匹配字串來更新,但是如果numInterpolations引數設為一個正整數N時,那麼在替換時就只會在前N次匹配發生時替換變數會跟隨匹配物件來調整所代表的內容,但N次之後就以一致以第N次替換變數所代表內容來做為以後替換結果。

舉個例子會更好理解:

假如沿用以上例子中的正則表示式模式以及替換內容來進行替換工作,設目標字串為"Tank b123: 85 Tank b256: 32 Tank b78: 22",並且設numInterpolations引數為INTERPOLATE_ALL,而Util.substitute()方法中的numSub變數設為SUBSTITUTE_ALL(請參考上文Util.substitute()方法內容),那麼你獲得的替換結果將會是:
Tank a123- 85 Tank a256- 32 Tank a78- 22

但是如果你把numInterpolations設為2,並且numSubs依然設為SUBSTITUTE_ALL,那麼這時你獲得的結果則會是:
Tank a123- 85 Tank a256- 32 Tank a256- 22

你要注意到最後一個替換所用變數$1所代表的內容與第二個$1一樣為"256",而不是預期的"78",因為在替換進行中,替換變數$1只根據匹配內容進行了兩次更新,最後一次就使第二次匹配時所更新的結果,那麼我們可以由此知道,如果numInterpolations設為1,那麼結果將是:
Tank a123- 85 Tank a123- 32 Tank a123- 22

3.應用示例:

剛好前段時間公司準備出一個《伊索預言》的英語學習互動教材,其中有電子檔資料的整理工作,我們就以此為例來看一下Jakarta-ORO與JDBC2.0 API結合起來對資料庫內的資料進行簡單提取與整理的實現。假設由錄入部的同事送過來的存放在MS SQLSERVER 7資料庫裡的電子檔的表結構如下(注:或許在不同的DBMS中有相應的正則表示式的應用,但這不在本文討論範圍內):

表名:AESOP,表中每條記錄包含有三列:

ID(int):單詞索引號
WORD(varchar):單詞
CONTENT(varchar):存放單詞的相關解釋與例句等內容

其中CONTENT列中內容的格式如下:
[音標] [詞性] (解釋){(例句一/例句解釋/例句中該詞的詞性: 單詞在句中的意思) (例句二/例句解釋/例句中該詞的詞性: 單詞在句中的意思)}

如對應單詞Kevin,CONTENT中的內容如下:
['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)( Kevin is living in ZhuHai now./凱文現住在珠海/名詞: 凱文)}

我們的例子主要針對CONTENT列中內容進行字串處理。

★查詢單個匹配:

首先,讓我們嘗試把CONTNET列中的[音標]欄位的內容列示出來,由於所有單詞的記錄中都有這一項並且都在字串開始位置,所以這個查詢工作比較簡單:

1、確定相應的正則表示式:/[[^]]+/]
這個是很簡單的正則表示式,其意思是要求相匹配的字串必須為以一對中括號包含的所有內容,如['kevin] 、[名詞]等,但內容中不包括"]"符號,也就是要避免出現"[][]"會作為一個匹配物件的情況出現(有關正則表示式的基礎知識請參照有關資料,這裡不再詳述)。

注意,在Java中,你必須對每一個向前的斜槓("/")進行轉義處理。所以我們要在上面的正則表示式裡每個"/"前面加上一個"/"以免出現編譯錯誤,也就是在JAVA中初始化正則表示式的字串的語句應該為:

String restring=" //[[^]]+//]";

並且在表示式裡每個符號中間不能有空格,否則就會同樣出現編譯錯誤。

2、例項化PatternCompiler物件,建立Pattern物件
PatternCompiler compiler=new Perl5Compiler();

Pattern pattern=compiler.compile(restring);

3、建立PatternMatcher物件,呼叫PatternMatcher介面的contain()方法檢查匹配情況:

PatternMatcher matcher=new Perl5Matcher();
if (matcher.contains(content,pattern)) {
//處理程式碼片段
}

這裡matcher.contains(content,pattern)中的引數 content是從資料庫裡取來的字串變數。該方法只會查到第一個匹配的物件字串,但是由於音標項均在CONETNET內容字串中的起始位置,所以用這個方法就已經可以保證把每條記錄裡的音標項找出來了,但更為直接與合理的辦法是使用boolean matchesPrefix(PatternMatcherInput input,Pattern pattern)方法,該方法驗證目標字串是否以正則表示式所匹配的字串為起始。

具體實現的完整的程式程式碼如下:

package RegularExpressions;

//import……
import org.apache.oro.text.regex.*;


//使用Jakarta-ORO正則表示式庫前需要把它加到CLASSPATH裡面,如果用IDE是//JBUILDER,那麼也可以在JBUILDER裡直接自建新庫。
public class yisuo {
 public static void main(String[] args) {
  try {
   //使用JDBC DRIVER進行DBMS連線,這裡我使用的是一個第三方JDBC
   //DRIVER,Microsoft本身也有一個面向SQLSERVER7/2000的免費JDBC //DRIVER,但其效能真的是奇差,不用也罷。
   Class.forName("com.jnetdirect.jsql.JSQLDriver");

   Connection con = DriverManager.getConnection("jdbc:JSQLConnect://kevin:1433","kevin chen","re");
   Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);

   //為使用Jakarta-ORO庫而建立相應的物件
   String rsstring = " //[[^]]+//]";
   PatternCompiler orocom = new Perl5Compiler();
   Pattern pattern = orocom.compile(rsstring);
   PatternMatcher matcher = new Perl5Matcher();
   ResultSet uprs = stmt.executeQuery("SELECT * FROM aesop");

   while (uprs.next()) {
    Stirng word = uprs.getString("word");
    Stirng content = uprs.getString("content");

    if (matcher.contains(content,pattern)) {
     //或if(matcher.matchesPrefix(content,pattern)){
     MatchResult result = matcher.getMatch();
     Stirng pure = result.toString();
     System.out.println(word + "的音標為:" + pure);
    }
   }
  } catch (Exception e) {
   System.out.println(e);
  }
 }
}

輸出結果為:kevin的音標為['kevin]

在這個處理中我是用toString()方法來取得結果,但是如果正則表示式裡是用了分組符號(圓括號),那麼就可以用group(int gid)的方法來取得相應各組匹配的結果,如正則表示式改為" (/[[^]]+/])",那麼就可以用以下方法來取得結果:pure=result.group(0);

用程式驗證,輸出結果同樣為:kevin的音標為['kevin]

而如果正則表示式為(/[[^]]+/])(/[[^]]+/]),則會查詢到兩個連續的方括號所包含的內容,也就找到[音標] [詞性]兩項,但是兩項的結果分別在兩個組裡面,分別由下面語句獲得結果:

result.group(0)->返回[音標] [詞性]兩項內容,也就是與整個正則表示式相匹配的結果字串,在這裡也就為['kevin] [名詞]

result.group(1) ->返回[音標]項內容,結果應是['kevin]

result.group(2) ->返回[詞性]項內容,結果應是[名詞]

繼續用程式驗證,發現輸出並不正確,主要是當內容有中文時就不能成功匹配,考慮到可能是Jakarta-ORO正則表示式庫版本不支援中文的問題,回看一下原來我一直用的還是2.0.1的老版本,馬上到Jakarta.org上下載最新的2.0.4版本裝上再用程式驗證,得出的結果就和預期一樣正確。

★查詢多個匹配:
經過第一步的嘗試使用Jakarta-ORO後,我們已經知道了如何正確使用該API包來查詢目標字串裡一個匹配的子串,下面我們接著來看一看當目標字串裡包含不止一個匹配的子串時我們如何把它們一個接一個找出來進行相應的處理。

首先我們先試個簡單的應用,假設我們想把CONTNET欄位內容裡所有用方括號包起來的字串都找出來,很清楚地,CONTNET欄位的內容裡面就只有兩項匹配的內容:[音標]和 [詞性],剛才我們其實已經把它們分別找出來了,但是我們所用的方法是分組方法,把"[音標] [詞性]"作為一整個正則表示式匹配的內容先找到,再根據分組把[音標]和 [詞性]分別挑出來。但是現在我們需要做的是把[音標]和[詞性]分別做為與同一個正則表示式匹配的內容,先找到一個接著再找下一個,也就是剛才我們的表示式為(/[[^]]+/])(/[[^]]+/]),而現在應為" /[[^]]+/] "。

我們已經知道在匹配操作的三個方法裡只要用PatternMatcherInput物件作為引數替代String物件就可以從字串中最後一次匹配的位置開始繼續進行匹配,實現的程式片段如下:

PatternMatcherInput input=new PatternMatcherInput(content);
while (matcher.contains(input,pattern)) {
result=matcher.getMatch();
System.out.println(result.group(0))
}

輸出結果為:['kevin]

[名詞]

接著我們來做複雜一點的處理,就是我們要先把下面內容:
['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)( Kevin is living in ZhuHai now. /凱文現住在珠海/名詞: 凱文)}中的整個例句部分(也就是由大括號所包含的部分)找出來,再分別把例句一和例句二找出,而各例句中的各項內容(英文句、中文句、詞性、解釋)也要分項列出。

第一步當然是要定出相應的正則表示式,需要有兩個,一是和整個例句部分(也就是由大括號包起來的部分)匹配的正則表示式:"/{.+/}",
另一個則要和每個例句部分匹配(也就是小括號中的內容),:/(([^)]+/)

而且由於要把例句的各項分離出來,所以要再把裡面的各部分用分組的方法匹配出來:" ([^(]+)/(.+)/(.+):([^)]+) "。
為了簡便起見,我們不再和從資料庫裡讀出,而是構造一個包含同樣內容的字串變數,程式片段如下:

try{
String content="['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞:凱文) (Kevin is living in ZhuHai now./凱文現住在珠海/名詞: 凱文)}";
String ps1="//{.+//}";
String ps2="//([^)]+//)";
String ps3="([^(]+)/(.+)/(.+):([^)]+)";
String sentence;
PatternCompiler orocom=new Perl5Compiler();
Pattern pattern1=orocom.compile(ps1);
Pattern pattern2=orocom.compile(ps2);
Pattern pattern3=orocom.compile(ps3);
PatternMatcher matcher=new Perl5Matcher();
//先找出整個例句部分
if (matcher.contains(content,pattern1)) {
MatchResult result=matcher.getMatch();
String example=result.toString();
PatternMatcherInput input=new PatternMatcherInput(example);
//分別找出例句一和例句二
while (matcher.contains(input,pattern2)){
result=matcher.getMatch();
sentence=result.toString();
//把每個例句裡的各項用分組的辦法分隔出來
if (matcher.contains(sentence,pattern3)){
result=matcher.getMatch();
System.out.println("英文句: "+result.group(1));
System.out.println("句子中文翻譯: "+result.group(2));
System.out.println("詞性: "+result.group(3));
System.out.println("意思: "+result.group(4));
}
}
}
}
catch(Exception e) {
System.out.println(e);
}

輸出結果為:
英文句: Kevin loves comic.
句子中文翻譯: 凱文愛漫畫
詞性: 名詞
意思: 凱文
英文句: Kevin is living in ZhuHai now.
句子中文翻譯: 凱文現住在珠海
詞性: 名詞
意思: 凱文

★查詢替換:
以上的兩個應用都是單純在查詢字串匹配方面的,我們再來看一下查詢後如何對目標字串進行替換。

例如我現在想把第二個例句進行改動,換為:Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因為它是一部好電影。/名詞:凱文。

也就是把
['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)( Kevin is living in ZhuHai now. /凱文現住在珠海/名詞: 凱文)}

改為:
['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)( Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因為它是一部好電影。/名詞:凱文。)}

之前,我們已經瞭解Util.substitute()方法與Substiution介面,以及Substiution的兩個實現類StringSubstitution和Perl5Substitution,我們就來看看怎麼用Util.substitute()方法配合Perl5Substitution來完成我們上面提出的替換要求,確定正則表示式:

我們要先找到其中的整個例句部分,也就是由大括號包起來的字串,並且把兩個例句分別分組,所以正則表示式為:"/{(/([^)]+/))(/([^)]+/))/}",如果用替換變數來代替分組,那麼上面的表示式可以看為"/{$1$2/}",這樣就可以更容易看出替換變數與分組間的關係。

根據上面的正則表示式Perl5Substitution類可以這樣構造:
Perl5Substitution("{$1( Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因為它是一部好電影。/名詞:凱文。)}")

再根據這個Perl5Substitution物件來使用Util.substitute()方法便可以完成替換了,實現的程式碼片段如下:

try{
String content="['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)(Kevin lives in ZhuHai now./凱文現住在珠海/名詞: 凱文)}";
String ps1="//{(//([^)]+//))(//([^)]+//))//}";
String sentence;
String pure;
PatternCompiler orocom=new Perl5Compiler();
Pattern pattern1=orocom.compile(ps1);
PatternMatcher matcher=new Perl5Matcher();
String result=Util.substitute(matcher,pattern1,new Perl5Substitution(
"{$1( Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因為它是一部好電影。/名詞:凱文。)}",1),content,Util.SUBSTITUTE_ALL);
System.out.println(result);
}
catch(Exception e) {
System.out.println(e);
}

輸出結果是正確的,為:
['kevin] [名詞](人名凱文){(Kevin loves comic./凱文愛漫畫/名詞: 凱文)( Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因為它是一部好電影。/名詞:凱文。)}

至於有關使用numInterpolations引數的構造器用法,讀者只要根據上面的介紹自己動手試一下就會清楚了,在此就不再例述。

總結:

本文首先介紹了Jakarta-ORO正則表示式庫的物件與方法,並且接著舉例讓讀者對實際應用有進一步的瞭解,雖然例子都比較簡單,但希望讀者們在看了該文後對Jakarta-ORO正則表示式庫有一定的認知,在實際工作中有所幫助與啟發。

其實在Jakarta org裡除了Jakarta-ORO外還有一個百分百的純JAVA正則表示式庫,就是由Jonathan Locke贈與Jakarta ORG的Regexp,在該包裡面包含了完整的文件以及一個用於除錯的Applet例子,對其有興趣的讀者可以到此下載。

參考資料:

本文的主要參考文章,該文在介紹Jakarta-ORO的同時也為讀者詳盡解析了正則表示式的基本語法。
一個基於PERL的正則表示式詳盡教程(雖然該教程是基於PERL的,但是你並不需要有PERL的經驗,雖然那會有所幫助),以及一個不錯的正則表示式簡例教程。
最不可缺少的當然是Jakarta-ORO的幫助文件http://jakarta.apache.org/oro/api/

關於作者
陳廣佳 Kevin Chen,汕頭大學電子資訊工程系工科學士,臺灣大新出版社珠海區開發部,現正圍繞中日韓電子資料使用JAVA開發電子詞典等相關專案。可通過E-mail:[email protected]於他聯絡。

java.util.regex篇

現在JDK1.4裡終於有了自己的正則表示式API包,JAVA程式設計師可以免去找第三方提供的正則表示式庫的周折了,我們現在就馬上來了解一下這個SUN提供的遲來恩物- -對我來說確實如此。

1.簡介:

java.util.regex是一個用正則表示式所訂製的模式來對字串進行匹配工作的類庫包。

它包括兩個類:Pattern和Matcher

Pattern 一個Pattern是一個正則表示式經編譯後的表現模式。
Matcher 一個Matcher物件是一個狀態機器,它依據Pattern物件做為匹配模式對字串展開匹配檢查。

首先一個Pattern例項訂製了一個所用語法與PERL的類似的正則表示式經編譯後的模式,然後一個Matcher例項在這個給定的Pattern例項的模式控制下進行字串的匹配工作。

以下我們就分別來看看這兩個類:

2.Pattern類:

Pattern的方法如下:

static Pattern compile(String regex)
將給定的正則表示式編譯並賦予給Pattern類
static Pattern compile(String regex,int flags)
同上,但增加flag引數的指定,可選的flag引數包括:CASE INSENSITIVE,MULTILINE,DOTALL,UNICODE CASE, CANON EQ
int flags()
返回當前Pattern的匹配flag引數.
Matcher matcher(CharSequence input)
生成一個給定命名的Matcher物件
static boolean matches(String regex,CharSequence input)
編譯給定的正則表示式並且對輸入的字串以該正則表示式為模開展匹配,該方法適合於該正則表示式只會使用一次的情況,也就是隻進行一次匹配工作,因為這種情況下並不需要生成一個Matcher例項。
String pattern()
返回該Patter物件所編譯的正則表示式。
String[] split(CharSequence input)
將目標字串按照Pattern裡所包含的正則表示式為模進行分割。
String[] split(CharSequence input,int limit)
作用同上,增加引數limit目的在於要指定分割的段數,如將limi設為2,那麼目標字串將根據正則表示式分為割為兩段。

一個正則表示式,也就是一串有特定意義的字元,必須首先要編譯成為一個Pattern類的例項,這個Pattern物件將會使用 matcher()方法來生成一個Matcher例項,接著便可以使用該 Matcher例項以編譯的正則表示式為基礎對目標字串進行匹配工作,多個Matcher是可以共用一個Pattern物件的。

現在我們先來看一個簡單的例子,再通過分析它來了解怎樣生成一個Pattern物件並且編譯一個正則表示式,最後根據這個正則表示式將目標字串進行分割:

import java.util.regex.*;
public class Replacement{
public static void main(String[] args) throws Exception {
// 生成一個Pattern,同時編譯一個正則表示式
Pattern p = Pattern.compile("[/]+");
//用Pattern的split()方法把字串按"/"分割
String[] result = p.split(
"Kevin has seen《LEON》seveal times,because it is a good film."
+"/ 凱文已經看過《這個殺手不太冷》幾次了,因為它是一部"
+"好電影。/名詞:凱文。");
for (int i=0; i<result.length; i++)
System.out.println(result[i]);
}
}

輸出結果為:

Kevin has seen《LEON》seveal times,because it is a good film.
凱文已經看過《這個殺手不太冷》幾次了,因為它是一部好電影。
名詞:凱文。

很明顯,該程式將字串按"/"進行了分段,我們以下再使用 split(CharSequence input,int limit)方法來指定分段的段數,程式改動為:

tring[] result = p.split("Kevin has seen《LEON》seveal times,because it is a good film./ 凱文已經看過《這個殺手不太冷》幾次了,因為它是一部好電影。/名詞:凱文。",2);

這裡面的引數"2"表明將目標語句分為兩段。

輸出結果則為:

Kevin has seen《LEON》seveal times,because it is a good film.

凱文已經看過《這個殺手不太冷》幾次了,因為它是一部好電影。/名詞:凱文。

由上面的例子,我們可以比較出java.util.regex包在構造Pattern物件以及編譯指定的正則表示式的實現手法與我們在上一篇中所介紹的Jakarta-ORO 包在完成同樣工作時的差別,Jakarta-ORO 包要先構造一個PatternCompiler類物件接著生成一個Pattern物件,再將正則表示式用該PatternCompiler類的compile()方法來將所需的正則表示式編譯賦予Pattern類:

PatternCompiler orocom=new Perl5Compiler();
Pattern pattern=orocom.compile("REGULAR EXPRESSIONS");
PatternMatcher matcher=new Perl5Matcher();

但是在java.util.regex包裡,我們僅需生成一個Pattern類,直接使用它的compile()方法就可以達到同樣的效果:
Pattern p = Pattern.compile("[/]+");

因此似乎java.util.regex的構造法比Jakarta-ORO更為簡潔並容易理解。

3.Matcher類:

Matcher方法如下:

Matcher appendReplacement(StringBuffer sb,String replacement)
將當前匹配子串替換為指定字串,並且將替換後的子串以及其之前到上次匹配子串之後的字串段新增到一個StringBuffer物件裡。
StringBuffer appendTail(StringBuffer sb)
將最後一次匹配工作後剩餘的字串新增到一個StringBuffer物件裡。
int end()
返回當前匹配的子串的最後一個字元在原目標字串中的索引位置 。
int end(int group)
返回與匹配模式裡指定的組相匹配的子串最後一個字元的位置。
boolean find()
嘗試在目標字串裡查詢下一個匹配子串。
boolean find(int start)
重設Matcher物件,並且嘗試在目標字串裡從指定的位置開始查詢下一個匹配的子串。
String group()
返回當前查詢而獲得的與組匹配的所有子串內容
String group(int group)
返回當前查詢而獲得的與指定的組匹配的子串內容
int groupCount()
返回當前查詢所獲得的匹配組的數量。
boolean lookingAt()
檢測目標字串是否以匹配的子串起始。
boolean matches()
嘗試對整個目標字元展開匹配檢測,也就是隻有整個目標字串完全匹配時才返回真值。
Pattern pattern()
返回該Matcher物件的現有匹配模式,也就是對應的Pattern 物件。
String replaceAll(String replacement)
將目標字串裡與既有模式相匹配的子串全部替換為指定的字串。
String replaceFirst(String replacement)
將目標字串裡第一個與既有模式相匹配的子串替換為指定的字串。
Matcher reset()
重設該Matcher物件。
Matcher reset(CharSequence input)
重設該Matcher物件並且指定一個新的目標字串。
int start()
返回當前查詢所獲子串的開始字元在原目標字串中的位置。
int start(int group)
返回當前查詢所獲得的和指定組匹配的子串的第一個字元在原目標字串中的位置。

(光看方法的解釋是不是很不好理解?不要急,待會結合例子就比較容易明白了)

一個Matcher例項是被用來對目標字串進行基於既有模式(也就是一個給定的Pattern所編譯的正則表示式)進行匹配查詢的,所有往Matcher的輸入都是通過CharSequence介面提供的,這樣做的目的在於可以支援對從多元化的資料來源所提供的資料進行匹配工作。

我們分別來看看各方法的使用:

★matches()/lookingAt ()/find():
一個Matcher物件是由一個Pattern物件呼叫其matcher()方法而生成的,一旦該Matcher物件生成,它就可以進行三種不同的匹配查詢操作:

  1. matches()方法嘗試對整個目標字元展開匹配檢測,也就是隻有整個目標字串完全匹配時才返回真值。
  2. lookingAt ()方法將檢測目標字串是否以匹配的子串起始。
  3. find()方法嘗試在目標字串裡查詢下一個匹配子串。

以上三個方法都將返回一個布林值來表明成功與否。

★replaceAll ()/appendReplacement()/appendTail():

Matcher類同時提供了四個將匹配子串替換成指定字串的方法:

  1. replaceAll()
  2. replaceFirst()
  3. appendReplacement()
  4. appendTail()

replaceAll()與replaceFirst()的用法都比較簡單,請看上面方法的解釋。我們主要重點了解一下appendReplacement()和appendTail()方法。

appendReplacement(StringBuffer sb,String replacement) 將當前匹配子串替換為指定字串,並且將替換後的子串以及其之前到上次匹配子串之後的字串段新增到一個StringBuffer物件裡,而appendTail(StringBuffer sb) 方法則將最後一次匹配工作後剩餘的字串新增到一個StringBuffer物件裡。

例如,有字串fatcatfatcatfat,假設既有正則表示式模式為"cat",第一次匹配後呼叫appendReplacement(sb,"dog"),那麼這時StringBuffer sb的內容為fatdog,也就是fatcat中的cat被替換為dog並且與匹配子串前的內容加到sb裡,而第二次匹配後呼叫appendReplacement(sb,"dog"),那麼sb的內容就變為fatdogfatdog,如果最後再呼叫一次appendTail(sb),那麼sb最終的內容將是fatdogfatdogfat。

還是有點模糊?那麼我們來看個簡單的程式:

//該例將把句子裡的"Kelvin"改為"Kevin"
import java.util.regex.*;
public class MatcherTest{
public static void main(String[] args)
throws Exception {
//生成Pattern物件並且編譯一個簡單的正則表示式"Kelvin"
Pattern p = Pattern.compile("Kevin");
//用Pattern類的matcher()方法生成一個Matcher物件
Matcher m = p.matcher("Kelvin Li and Kelvin Chan are both working in Kelvin Chen's KelvinSoftShop company");
StringBuffer sb = new StringBuffer();
int i=0;
//使用find()方法查詢第一個匹配的物件
boolean result = m.find();
//使用迴圈將句子裡所有的kelvin找出並替換再將內容加到sb裡
while(result) {
i++;
m.appendReplacement(sb,"Kevin");
System.out.println("第"+i+"次匹配後sb的內容是:"+sb);
//繼續查詢下一個匹配物件
result = m.find();
}
//最後呼叫appendTail()方法將最後一次匹配後的剩餘字串加到sb裡;
m.appendTail(sb);
System.out.println("呼叫m.appendTail(sb)後sb的最終內容是:"+ sb.toString());
}
}

最終輸出結果為:
第1次匹配後sb的內容是:Kevin
第2次匹配後sb的內容是:Kevin Li and Kevin
第3次匹配後sb的內容是:Kevin Li and Kevin Chan are both working in Kevin
第4次匹配後sb的內容是:Kevin Li and Kevin Chan are both working in Kevin Chen's Kevin
呼叫m.appendTail(sb)後sb的最終內容是:Kevin Li and Kevin Chan are both working in Kevin Chen's KevinSoftShop company.

看了上面這個例程是否對appendReplacement(),appendTail()兩個方法的使用更清楚呢,如果還是不太肯定最好自己動手寫幾行程式碼測試一下。

★group()/group(int group)/groupCount():
該系列方法與我們在上篇介紹的Jakarta-ORO中的MatchResult .group()方法類似(有關Jakarta-ORO請參考上篇的內容),都是要返回與組匹配的子串內容,下面程式碼將很好解釋其用法:

import java.util.regex.*;
public class GroupTest{
public static void main(String[] args)
throws Exception {
Pattern p = Pattern.compile("(ca)(t)");
Matcher m = p.matcher("one cat,two cats in the yard");
StringBuffer sb = new StringBuffer();
boolean result = m.find();
System.out.println("該次查詢獲得匹配組的數量為:"+m.groupCount());
for(int i=1;i<=m.groupCount();i++){
System.out.println("第"+i+"組的子串內容為: "+m.group(i));
}
}
}

輸出為:
該次查詢獲得匹配組的數量為:2
第1組的子串內容為:ca
第2組的子串內容為:t

Matcher物件的其他方法因比較好理解且由於篇幅有限,請讀者自己程式設計驗證。

4.一個檢驗Email地址的小程式:
最後我們來看一個檢驗Email地址的例程,該程式是用來檢驗一個輸入的EMAIL地址裡所包含的字元是否合法,雖然這不是一個完整的EMAIL地址檢驗程式,它不能檢驗所有可能出現的情況,但在必要時您可以在其基礎上增加所需功能。

import java.util.regex.*;
public class Email {
public static void main(String[] args) throws Exception {
String input = args[0];
//檢測輸入的EMAIL地址是否以 非法符號"."或"@"作為起始字元
Pattern p = Pattern.compile("^//.|^//@");
Matcher m = p.matcher(input);
if (m.find()){
System.err.println("EMAIL地址不能以'.'或'@'作為起始字元");
}
//檢測是否以"www."為起始
p = Pattern.compile("^www//.");
m = p.matcher(input);
if (m.find()) {
System.out.println("EMAIL地址不能以'www.'起始");
}
//檢測是否包含非法字元
p = Pattern.compile("[^A-Za-z0-9//.//@_//-~#]+");
m = p.matcher(input);
StringBuffer sb = new StringBuffer();
boolean result = m.find();
boolean deletedIllegalChars = false;
while(result) {
//如果找到了非法字元那麼就設下標記
deletedIllegalChars = true;
//如果裡面包含非法字元如冒號雙引號等,那麼就把他們消去,加到SB裡面
m.appendReplacement(sb,"");
result = m.find();
}
m.appendTail(sb);
input = sb.toString();
if (deletedIllegalChars) {
System.out.println("輸入的EMAIL地址裡包含有冒號、逗號等非法字元,請修改");
System.out.println("您現在的輸入為: "+args[0]);
System.out.println("修改後合法的地址應類似: "+input);
}
}
}

例如,我們在命令列輸入:java Email [email protected]

那麼輸出結果將會是:EMAIL地址不能以'www.'起始

如果輸入的EMAIL為@[email protected]

則輸出為:EMAIL地址不能以'.'或'@'作為起始字元

當輸入為:cgjmail#$%@163.net

那麼輸出就是:

輸入的EMAIL地址裡包含有冒號、逗號等非法字元,請修改
您現在的輸入為: cgjmail#$%@163.net
修改後合法的地址應類似: [email protected]

5.總結:
本文介紹了jdk1.4.0-beta3里正則表示式庫--java.util.regex中的類以及其方法,如果結合與上一篇中所介紹的Jakarta-ORO API作比較,讀者會更容易掌握該API的使用,當然該庫的效能將在未來的日子裡不斷擴充套件,希望獲得最新資訊的讀者最好到及時到SUN的網站去了解。

6.結束語:
本來計劃再多寫一篇介紹一下需付費的正則表示式庫中較具代表性的作品,但覺得既然有了免費且優秀的正則表示式庫可以使用,何必還要去找需付費的呢,相信很多讀者也是這麼想的:,所以有興趣瞭解更多其他的第三方正則表示式庫的朋友可以自己到網上查詢或者到我在參考資料裡提供的網址去看看。

參考資料

java.util.regex的幫助文件
Dana Nourie 和Mike McCloskey所寫的Regular Expressions and the Java" Programming Language
需要更多的第三方正則表示式資源以及基於它們所開發的應用程式請看