JAVA 正則表達式 (超詳細)

(PS:這篇文章爲轉載,我不喜歡轉載的但我以爲這篇文章實在是超讚了,就轉了過來,這篇能夠說是學習JAVA正則表達的必讀篇。做者是個正真有功力的人,閱讀愉快)



  在Sun的Java JDK 1.40版本中,Java自帶了支持正則表達式的包,本文就拋磚引玉地介紹瞭如何使用java.util.regex包。 html

  可粗略估計一下,除了偶爾用Linux的外,其餘Linu x用戶都會遇到正則表達式。正則表達式是個極端強大工具,並且在字符串模式-匹配和字符串模式-替換方面富有彈性。在Unix世界裏,正則表達式幾乎沒有什麼限制,可確定的是,它應用很是之普遍。 java

  正則表達式的引擎已被許多普通的Unix工具所實現,包括grep,awk,vi和Emacs等。此外,許多使用比較普遍的腳本語言也支持正則表達式,好比Python,Tcl,JavaScript,以及最著名的Perl。 程序員

  我很早之前就是個Perl方面的黑客,若是你和我同樣話,你也會很是依賴你手邊的這些強大的text-munging工具。近幾年來,像其餘程序開發者同樣,我也愈來愈關注Java的開發。 正則表達式

  Java做爲一種開發語言,有許多值得推薦的地方,可是它一直以來沒有自帶對正則表達式的支持。直到最近,藉助於第三方的類庫,Java開始支持正則表達式,但這些第三方的類庫都不一致、兼容性差,並且維護代碼起來很糟糕。這個缺點,對我選擇Java做爲首要的開發工具來講,一直是個巨大的顧慮之處。 spring

  你能夠想象,當我知道Sun的Java JDK 1.40版本包含了java.util.regex(一個徹底開放、自帶的正則表達式包)時,是多麼的高興!很搞笑的說,我花好些時間去挖掘這個被隱藏起來的寶石。我很是驚奇的是,Java這樣的一個很大改進(自帶了java.util.regex包)爲何很少公開一點呢?! sql

  最近,Java雙腳都跳進了正則表達式的世界。java.util.regex包在支持正則表達也有它的過人之處,另外Java也提供詳細的相關說明文檔。使得朦朦朧朧的regex神祕景象也慢慢被撥開。有一些正則表達式的構成(可能最顯著的是,在於糅合了字符類庫)在Perl都找不到。 數據庫

  在regex包中,包括了兩個類,Pattern(模式類)和Matcher(匹配器類)。Pattern類是用來表達和陳述所要搜索模式的對象,Matcher類是真正影響搜索的對象。另加一個新的例外類,PatternSyntaxException,當遇到不合法的搜索模式時,會拋出例外。 express

  即便對正則表達式很熟悉,你會發現,經過java使用正則表達式也至關簡單。要說明的一點是,對那些被Perl的單行匹配所寵壞的Perl狂熱愛好者來講,在使用java的regex包進行替換操做時,會比他們因此前經常使用的方法費事些。 apache

  本文的侷限之處,它不是一篇正則表達式用法的徹底教程。若是讀者要對正則表達進一步瞭解的話,推薦閱讀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。它提供了StringStringBuffer這兩個類的字符序列的抽象:


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

爲了實現這個新的CharSequence接口,StringStringBuffer以及CharBuffer都做了修改。不少正則表達式的操做都要拿CharSequence做參數。

PatternMatcher

先給一個例子。下面這段程序能夠測試正則表達式是否匹配字符串。第一個參數是要匹配的字符串,後面是正則表達式。正則表達式能夠有多個。在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.regexPatternMatcher類實現的Pattern對象表示經編譯的正則表達式。靜態的compile( )方法負責將表示正則表達式的字符串編譯成Pattern對象。正如上述例程所示的,只要給Patternmatcher( )方法送一個字符串就能獲取一個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( ),只有在字符串與正則表達式一開始就相匹配的狀況下才能返回truematches( )成功的前提是正則表達式與字符串徹底匹配,而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_INSENSITIVEPattern.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( )。這些方法的功能同PatternMatcher的相同。

替換操做

正則表達式在替換文本方面特別在行。下面就是一些方法:

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的功能是匹配'/*!' 和 '!*/' 之間的文本(注意一下分組用的括號)。接下來,咱們將全部兩個以上的連續空格全都替換成一個,而且將各行開頭的空格全都去掉(爲了讓這個正則表達式能對全部的行,而不只僅是第一行起做用,必須啓用多行模式)。這兩個操做都用了StringreplaceAll( )(這裏用它更方便)。注意,因爲每一個替換隻作一次,所以除了預編譯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設到當前字符串的開始處。


 

若是你曾經用過Perl或任何其餘內建正則表達式支持的語言,你必定知道用正則表達式處理文本和匹配模式是多麼簡單。若是你不熟悉這個術語,那麼「正則表達式」(Regular Expression)就是一個字符構成的串,它定義了一個用來搜索匹配字符串的模式。
許多語言,包括Perl、PHP、PythonJavaScriptJScript,都支持用正則表達式處理文本,一些文本編輯器用正則表達式實現高級「搜索-替換」功能。那麼Java又怎樣呢?本文寫做時,一個包含了用正則表達式進行文本處理的Java規範需求(Specification Request)已經獲得承認,你能夠期待在JDK的下一版本中看到它。
然而,若是如今就須要使用正則表達式,又該怎麼辦呢?你能夠從Apache.org下載源代碼開放的Jakarta-ORO庫。本文接下來的內容先簡要地介紹正則表達式的入門知識,而後以Jakarta-ORO API爲例介紹如何使用正則表達式。
1、正則表達式基礎知識
咱們先從簡單的開始。假設你要搜索一個包含字符「cat」的字符串,搜索用的正則表達式就是「cat」。若是搜索對大小寫不敏感,單詞「catalog」、「Catherine」、「sophisticated」均可以匹配。也就是說:
1.1 句點符號
假設你在玩英文拼字遊戲,想要找出三個字母的單詞,並且這些單詞必須以「t」字母開頭,以「n」字母結束。另外,假設有一本英文字典,你能夠用正則表達式搜索它的所有內容。要構造出這個正則表達式,你可使用一個通配符——句點符號「.」。這樣,完整的表達式就是「t.n」,它匹配「tan」、「ten」、「tin」和「ton」,還匹配「t#n」、「tpn」甚至「t n」,還有其餘許多無心義的組合。這是由於句點符號匹配全部字符,包括空格、Tab字符甚至換行符:
1.2 方括號符號
爲了解決句點符號匹配範圍過於普遍這一問題,你能夠在方括號(「[]」)裏面指定看來有意義的字符。此時,只有方括號裏面指定的字符才參與匹配。也就是說,正則表達式「t[aeio]n」只匹配「tan」、「Ten」、「tin」和「ton」。但「Toon」不匹配,由於在方括號以內你只能匹配單個字符:
1.3 「或」符號
若是除了上面匹配的全部單詞以外,你還想要匹配「toon」,那麼,你可使用「|」操做符。「|」操做符的基本意義就是「或」運算。要匹配「toon」,使用「t(a|e|i|o|oo)n」正則表達式。這裏不能使用方擴號,由於方括號只容許匹配單個字符;這裏必須使用圓括號「()」。圓括號還能夠用來分組,具體請參見後面介紹。
1.4 表示匹配次數的符號
表一顯示了表示匹配次數的符號,這些符號用來肯定緊靠該符號左邊的符號出現的次數:

假設咱們要在文本文件中搜索美國的社會安全號碼。這個號碼的格式是999-99-9999。用來匹配它的正則表達式如圖一所示。在正則表達式中,連字符(「-」)有着特殊的意義,它表示一個範圍,好比從0到9。所以,匹配社會安全號碼中的連字符號時,它的前面要加上一個轉義字符「/」。

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

假設進行搜索的時候,你但願連字符號能夠出現,也能夠不出現——即,999-99-9999和999999999都屬於正確的格式。這時,你能夠在連字符號後面加上「?」數量限定符號,如圖二所示:

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

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

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

1.5 「否」符號
「^」符號稱爲「否」符號。若是用在方括號內,「^」表示不想要匹配的字符。例如,圖四的正則表達式匹配全部單詞,但以「X」字母開頭的單詞除外。

圖四:匹配全部單詞,但「X」開頭的除外

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

圖五:匹配全部Moth DD,YYYY格式的日期

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

圖六:匹配全部Month DD,YYYY格式的日期,定義月份值爲第一個組

1.7 其它符號
爲簡便起見,你可使用一些爲常見正則表達式建立的快捷符號。如表二所示:
表二:經常使用符號

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

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

2、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對象。
▲ Pattern對象
要把正則表達式編譯成Pattern對象,調用compiler對象的compile()方法,並在調用參數中指定正則表達式。例如,你能夠按照下面這種方式編譯正則表達式「t[aeio]n」:
默認狀況下,編譯器建立一個大小寫敏感的模式(pattern)。所以,上面代碼編譯獲得的模式只匹配「tin」、「tan」、 「ten」和「ton」,但不匹配「Tin」和「taN」。要建立一個大小寫不敏感的模式,你應該在調用編譯器的時候指定一個額外的參數:
建立好Pattern對象以後,你就能夠經過PatternMatcher類用該Pattern對象進行模式匹配。
▲ PatternMatcher對象
PatternMatcher對象根據Pattern對象和字符串進行匹配檢查。你要實例化一個Perl5Matcher類並把結果賦值給PatternMatcher接口。Perl5Matcher類是PatternMatcher接口的一個實現,它根據Perl 5正則表達式語法進行模式匹配:
使用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)
3、應用實例
下面咱們來看看Jakarta-ORO庫的一些應用實例。
3.1 日誌文件處理
任務:分析一個Web服務器日誌文件,肯定每個用戶花在網站上的時間。在典型的BEA WebLogic日誌文件中,日誌記錄的格式以下:
分析這個日誌記錄,能夠發現,要從這個日誌文件提取的內容有兩項:IP地址和頁面訪問時間。你能夠用分組符號(圓括號)從日誌記錄提取出IP地址和時間標記。
首先咱們來看看IP地址。IP地址有4個字節構成,每個字節的值在0到255之間,各個字節經過一個句點分隔。所以,IP地址中的每個字節有至少一個、最多三個數字。圖八顯示了爲IP地址編寫的正則表達式:

圖八:匹配IP地址

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

圖九:匹配至少一個字符,直至找到「]」

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

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

如今正則表達式已經編寫完畢,接下來能夠編寫使用正則表達式庫的Java代碼了。
爲使用Jakarta-ORO庫,首先建立正則表達式字符串和待分析的日誌記錄字符串:
這裏使用的正則表達式與圖十的正則表達式差很少徹底相同,但有一點例外:在Java中,你必須對每個向前的斜槓(「/」)進行轉義處理。圖十不是Java的表示形式,因此咱們要在每一個「/」前面加上一個「/」以避免出現編譯錯誤。遺憾的是,轉義處理過程很容易出現錯誤,因此應該當心謹慎。你能夠首先輸入未經轉義處理的正則表達式,而後從左到右依次把每個「/」替換成「//」。若是要複檢,你能夠試着把它輸出到屏幕上。
初始化字符串以後,實例化PatternCompiler對象,用PatternCompiler編譯正則表達式建立一個Pattern對象:
如今,建立PatternMatcher對象,調用PatternMatcher接口的contain()方法檢查匹配狀況:
接下來,利用PatternMatcher接口返回的MatchResult對象,輸出匹配的組。因爲logEntry字符串包含匹配的內容,你能夠看到類以下面的輸出:
3.2 HTML處理實例一
下面一個任務是分析HTML頁面內FONT標記的全部屬性。HTML頁面內典型的FONT標記以下所示:
程序將按照以下形式,輸出每個FONT標記的屬性:
在這種狀況下,我建議你使用兩個正則表達式。第一個如圖十一所示,它從字體標記提取出「"face="Arial, Serif" size="+2" color="red"」。

圖十一:匹配FONT標記的全部屬性

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

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

分割結果爲:
如今咱們來看看完成這個任務的Java代碼。首先建立兩個正則表達式字符串,用Perl5Compiler把它們編譯成Pattern對象。編譯正則表達式的時候,指定Perl5Compiler.CASE_INSENSITIVE_MASK選項,使得匹配操做不區分大小寫。
接下來,建立一個執行匹配操做的Perl5Matcher對象。
假設有一個String類型的變量html,它表明了HTML文件中的一行內容。若是html字符串包含FONT標記,匹配器將返回true。此時,你能夠用匹配器對象返回的MatchResult對象得到第一個組,它包含了FONT的全部屬性:
接下來建立一個PatternMatcherInput對象。這個對象容許你從最後一次匹配的位置開始繼續進行匹配操做,所以,它很適合於提取FONT標記內屬性的名字-值對。建立PatternMatcherInput對象,以參數形式傳入待匹配的字符串。而後,用匹配器實例提取出每個FONT的屬性。這經過指定PatternMatcherInput對象(而不是字符串對象)爲參數,反覆地調用PatternMatcher對象的contains()方法完成。PatternMatcherInput對象之中的每一次迭代將把它內部的指針向前移動,下一次檢測將從前一次匹配位置的後面開始。
本例的輸出結果以下:
3.3 HTML處理實例二
下面咱們來看看另外一個處理HTML的例子。這一次,咱們假定Web服務器從widgets.acme.com移到了newserver.acme.com。如今你要修改一些頁面中的連接:
執行這個搜索的正則表達式如圖十三所示:

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

若是可以匹配這個正則表達式,你能夠用下面的內容替換圖十三的連接:
注意#字符的後面加上了$1。Perl正則表達式語法用$一、$2等表示已經匹配且提取出來的組。圖十三的表達式把全部做爲一個組匹配和提取出來的內容附加到連接的後面。
如今,返回Java。就象前面咱們所作的那樣,你必須建立測試字符串,建立把正則表達式編譯到Pattern對象所必需的對象,以及建立一個PatternMatcher對象:
接下來,用com.oroinc.text.regex包Util類的substitute()靜態方法進行替換,輸出結果字符串:
Util.substitute()方法的語法以下:
這個調用的前兩個參數是之前建立的PatternMatcher和Pattern對象。第三個參數是一個Substiution對象,它決定了替換操做如何進行。本例使用的是Perl5Substitution對象,它可以進行Perl5風格的替換。第四個參數是想要進行替換操做的字符串,最後一個參數容許指定是否替換模式的全部匹配子串(Util.SUBSTITUTE_ALL),或只替換指定的次數。
【結束語】在這篇文章中,我爲你介紹了正則表達式的強大功能。只要正確運用,正則表達式可以在字符串提取和文本修改中起到很大的做用。另外,我還介紹瞭如何在Java程序中經過Jakarta-ORO庫利用正則表達式。至於最終採用老式的字符串處理方式(使用StringTokenizer,charAt,和substring),仍是採用正則表達式,這就有待你本身決定了。


Jakarta-ORO篇

陳廣佳 (cgjmail@163.net)
電子信息工程系工科學士
2001 年 12 月

因爲工做的須要,本人常常要面對大量的文字電子資料的整理工做,所以曾對在JAVA中正則表達式的應用有所關注,並對其有必定的瞭解,但願經過本文與同行進行有關方面的心得交流。

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

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

因爲本文目的是要介紹如何在JAVA裏運用正則表達式,所以對剛接觸正則表達式的讀者請參考有關資料,在此因篇幅有限不做介紹。

JAVA對正則表達式的支持:
在JDK1.3或以前的JDK版本中並無包含正則表達式庫可供JAVA程序員使用,以前咱們通常都在使用第三方提供的正則表達式庫,這些第三方庫中有源代碼開放的,也有需付費購買的,而現時在JDK1.4的測試版中也已經包含有正則表達式庫---java.util.regex。

故此如今咱們有不少面向JAVA的正則表達式庫可供選擇,如下我將介紹兩個較具表明性的 Jakarta-OROjava.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時,上述三個方法的語法以下:

  1. boolean matches(PatternMatcherInput input, Pattern pattern)
  2. boolean matchesPrefix(PatternMatcherInput input, Pattern pattern)
  3. 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例子,對其有興趣的讀者能夠到此下載

參考資料:



關於做者
陳廣佳 Kevin Chen,汕頭大學電子信息工程系工科學士,臺灣大新出版社珠海區開發部,現正圍繞中日韓電子資料使用JAVA開發電子詞典等相關項目。可經過E-mail:cgjmail@163.net於他聯繫。


java.util.regex篇

陳廣佳 (cgjmail@163.net)
電子信息工程系工科學士
2001 年 12 月

如今JDK1.4裏終於有了本身的正則表達式API包,JAVA程序員能夠免去找第三方提供的正則表達式庫的周折了,咱們如今就立刻來了解一下這個SUN提供的遲來恩物- -對我來講確實如此。

1.簡介:
java.util.regex是一個用正則表達式所訂製的模式來對字符串進行匹配工做的類庫包。

它包括兩個類:PatternMatcher

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 www.kevin@163.net

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

若是輸入的EMAIL爲@kevin @163.net

則輸出爲:EMAIL地址不能以'.'或'@'做爲起始字符

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

那麼輸出就是:

輸入的EMAIL地址裏包含有冒號、逗號等非法字符,請修改
您如今的輸入爲: cgjmail#$%@163.net
修改後合法的地址應相似: cgjmail@163.net


5.總結:
本文介紹了jdk1.4.0-beta3里正則表達式庫--java.util.regex中的類以及其方法,若是結合與上一篇中所介紹的Jakarta-ORO API做比較,讀者會更容易掌握該API的使用,固然該庫的性能將在將來的日子裏不斷擴展,但願得到最新信息的讀者最好到及時到SUN的網站去了解。

6.結束語:
原本計劃再多寫一篇介紹一下需付費的正則表達式庫中較具表明性的做品,但以爲既然有了免費且優秀的正則表達式庫可使用,何須還要去找需付費的呢,相信不少讀者也是這麼想的:,因此有興趣瞭解更多其餘的第三方正則表達式庫的朋友能夠本身到網上查找或者到我在參考資料裏提供的網址去看看。

參考資料

相關文章
相關標籤/搜索