1. 程式人生 > >[150827]Groovy 之正則表示式

[150827]Groovy 之正則表示式

一提到 Perl,很多人對它嘖嘖稱道的就是它那強大的正則表示式。一段富含正則表示式的 Perl 程式碼能讓人覺得眼花繚亂,不過一旦習慣了就會對此愛不釋手。而 Java 是在 1.4(在之前的版本需要依靠 jakarta-oro 庫來提供對正則表式的支援--也是參考了 Perl) 中才加入了正則表示式,但它的相關操作還是略顯古板。

當從 Java 分化出 Groovy 指令碼語言一支,在處理正則表示式時也不想落後,從 Perl 那裡學來了一些更為緊湊的語法,使用起來方便多了。雖相比 Perl 還有些差距,基本還是能知足了。下面來介紹 Groovy 中如何使用正則表示式,還是從 Java 的正式表示式說開,好有個對比。

比如在 Java 中要檢查一段文字是否與某個模式相匹配,使用程式碼:

m="10.128.12.16" =~ /(/d.*)/./
if(m.find()){
    println m.group(1);
}

我們的 Groovy 處理正則表示式時,引入了類似於 Perl 的語法,與上面程式碼完全對應的 Groovy 的寫法就是

p = ~"(ab)*";           // ~ 符號要緊靠其他的字串,並與前面的等號有空格
m = "abababab" =~ p;    //=~ 是一個整體
println m.matches();  //輸出為 true

Groovy 中應用了下列規則:

1. ~"pattern" ---- 建立 Pattern 物件。它用來替換 Pattern.compile("pattern");

2. "text" =~ pattern ----建立 Matcher 物件。它相當於 pattern.matcher("text")。

我們還可以用更緊湊的語法來建立 Matcher 對像,那就是

m = "abababab" =~ "(ab)*"

=~ 是 Pattern.compile("pattern").matcher("text") 的替代。也由此可見,在  =~ 既能是一個 Pattern,也可以是一個字串,如果是字串就自動編譯成了一個 Pattern。

有了 Matcher 物件,就可以用標準的 Java 方式來使用了,如替換、取出分組字串等。

對於最前面例子中的三行程式碼,通過 Groovy 引入了 ==~ 的操作符就可以寫在一行裡了:

println "abababab" ==~ "(ab)*" //輸出為 true

==~ 就相當於 Pattern.compile("pattern").matcher("text").matchers(); 返回一個 boolean 值,三步為一體了。

注意:在建立 Pattern 對像時 ~ 符號緊貼正則表示式字串;建立 Matcher 時 =~ 是一個整體。


其他一些 Groovy 的正則表示式應用舉例:

1. Groovy 也可以像 JavaScript 裡那樣表示一個正則表示式。如

p = ~/Hello/;         //其實/Hello/ 也是一種 Groovy 的字串表示法
println p.class.name; //列印的是 'java.util.regex.Pattern'

其實就是一個 Groovy 字串("、'、"""、'''括起來的都行)。不過為了程式碼的可閱讀性,我們可針對正則表示式用 /Hello/ 的形式,一看就知道是個正則表示式。但是不能像 JavaScript 那樣,在第二個"/"後加 g 或者 i 來表示全域性或忽略大小寫,Groovy 中忽略大小寫的匹配要用 (?i:X),例如

println "Hello World" ==~ /(?i:hEllo.*)/;  //輸出為 true

2. =~ 的不同上下文。m = "Hello World!" =~ /Hello/; 語句構造的 m 是一個 Matcher 物件,不過要是我們給包上 if 語句或是斷言,就相當於執行了 Matcher 的 find() 方法,如

assert "Hello World!" =~ /Hello/  //找到模式,斷言成立
if("Hello World!" =~ /Hello/) 
   println "Found"                //找到模式,輸出 Found

3. 替換操作

s = "1.23".replaceAll(//d/){ num -> num.toInteger() + 1};
println s;   //數字遞增了 1,列印的 s 為 2.34

4. 用閉包輸出匹配值

finder = "10.128.12.16" =~ //d+/
finder.each{
   println finder[it]}
}

輸出結果為:

10
128
12
16

5. 貪婪和非貪婪匹配

一般情況下,正則表示式的匹配都是貪婪的,例如:

m="10.128.12.16" =~ /(/d.*)/./
if(m.find()){
    println m.group(1);
}

或許你希望上面的輸出是 10,只想讓 /(/d.*)/./ 中的 ".*" 到第一個點之前停下來,可事實不是這樣,這裡的 ".*" 會試圖吃盡所有的字元,到最後一個點之前才會停下來,所以上面程式碼的輸出是 10.128.12。那我們想要輸出是 10,該如何呢?你只要在 "*" 後加上一個 "?",即正則表示式寫成 /(/d.*?)/./ 即可,它就會在碰到的下一個點之前停下來。

m="10.128.12.16" =~ /(/d.*?)/./
if(m.find()){
    println m.group(1);   //星號加個問題,表示非貪婪匹配,輸出為 10
}

對於正則表示式中的其他量詞也是一樣的:/(/d.+)/./ 貪婪的; /(/d.+?)/./ 非貪婪的。/(/d.{1,})/./ 貪婪的;/(/d.{1,}?)/./ 非貪婪的,等等。

記住,只要在正則表達的量詞後加上一個問號“?” 就是非貪婪的。