1. 程式人生 > >Groovy 正則表示式

Groovy 正則表示式

文章目錄

背景

專案使用Gradle作為自動化構建的工具, 閒暇之餘對這個工具的使用方式以及其配置檔案依賴的Groovy語法進行了鞏固, 在學習Groovy語法的時候發現其中一個比較有意思的東西, 就是Groovy的正則表示式,於是本篇文章總結了一下Groovy中的正則表示式的特點以及Groovy正則表示式與Java正則表示式的區別:

Groovy正則表示式語法

Groovy是Java語言的一種擴充套件, 可以無縫的使用Java的JDK, 並且其自身還有SDK對Java進行了擴充套件. Groovy中的正則表示式本質上還是會使用到JDK中的java.lang.regex 包中的class, 其實這一部分, 個人認為可以看成一種"語法糖", 只不過更方便大家在Groovy中使用正則表示式
來看一個最簡單的正則表示式

def reg1 = ~'he*llo'
def reg2 = /he*llo/
println "reg1 type is ${reg1.class}"
println "reg2 type is ${reg2.class}"
println "hello".matches(reg1)
println "hello".matches(reg2)

執行結果:

reg1 type is class java.util.regex.Pattern
reg2 type is class java.lang.String
true
true

上式中使用了~ + 字串(以及雙斜線分隔符模式)的方式定義了一個正則表示式
Groovy中支援使用~ 來定義正則表示式, 打印出來的reg 型別都為Pattern 型別而不是一個字串, 需要注意的點是上述例子中的~= 之間有一個空格, 因為Groovy中存在=~ 操作符號, 這個操作符為查詢操作符, 使用在字串之後, 要求接一個正則表示式, 返回的是一個java.util.regex.Matcher

物件. 還有一個操作符==~ 也比較容易混淆,這個操作符為匹配操作符, 後面跟一個正則表示式, 返回的型別為Boolean 型別. 這個操作符要求前面給定的字串與後面的正則表示式完全匹配才可返回true 比如以下的列子

def val1 = "hello" =~ "he*llo"
println val1.class
print val1.matches()

執行結果

class java.util.regex.Matcher
true

使用Groovy中的匹配操作符可以簡化上述的操作

def val1 = "hello" ==~ "he*llo"
println val1.class
print val1

執行結果:

class java.lang.Boolean
true

原字元問題

我們知道正則表示式中存在一些特殊的字元(比如\w 表示的是[a-zA-Z0-9])用於文字的匹配, 這些字元一般是以\ 開頭, 所以這個地方涉及到了轉義字元問題. 舉個例子:

def val1 = "test value"
println 'value is ${val1}'
println "value is ${val1}"

執行結果:

value is ${val1}
value is test value

如果在構建正則表示式字串的時候, 使用雙引號表示字串,就需要使用\\ 來表示單斜線,比如:

def reg1 = "hello \\w*"
def reg2 = /hello \w*/
println "hello world" ==~ reg1
println "hello world" ==~ reg2

執行結果為true 當然使用雙斜線字串的話就不需要額外的斜線進行轉義. 我們知道groovy的單引號中的字串是以原字元的形式存在的,即是字串本身就是它顯示的意思,嘗試使用單引號原字元來進行正則匹配:

def reg1 = 'hello \w*' // 更改為 'hello \\w*' 則執行正確
println "hello world" ==~ reg1

但是最終卻是一個error 使用單引號依然需要進行轉義, 仔細想想,Groovy的單引號場景是引數解析的場景, 而此處是含有斜線的正則表示式字元的匹配問題, 兩個問題應該不一樣,因此無論是使用單引號還是雙引號,遇到正則表示式的含有斜線的特殊字元都要進行轉義.不想進行轉義可以使用斜線取代(單)雙引號.

Pattern 和 Matcher

在Groovy中正則表示式中相關聯的依然是這兩個Java類. 依然迴歸到Java中的這兩個類. JDK1.8 中java.util.regex 包中最核心的就是這兩個類, Pattern表示的即是正則表示式的"模式", 這是一個抽象的概念, 在程式設計過程中我們使用的是字串表示正則, 它只是一種抽象的模式,實際上需要將字串表示的抽象模式"編譯"成這個類才能夠正常工作, 當在Groovy中可以理解為使用~ 操作符將字串編譯為一個Pattern物件. 回顧這個類中的一些重要的概念和方法:

Pattern.matches 和 Pattern.matcher

Matcher matcher(charsequence input)
這個函式返回一個Matcher匹配器物件, 這個匹配器匹配給定的輸入與模式

def reg = ~/^hello \w*world$/
def str = "hello world"
def matcher = reg.matcher(str)
println matcher.class

輸出的型別就是java.util.regex.Matcher 然而上述的Matcher物件在groovy中可以用=~ 操作符號一步完成

def matcher = "hello world"=~/^hello \w*world$/
println matcher.class

static boolean matches(string regex, charsequence input)
這個函式編譯給定的正則表示式並且嘗試匹配給定的輸入, 這個在Java中是一個靜態的函式, 可以理解為一種快速判斷字串與給定的正則表示式模式是否匹配的工具, 同樣在Groovy中也有簡單的實現方式

println "hello world"==~/^hello \w*world$/

執行結果為true 可以看到Groovy中使用兩個操作符號=~==~ 完成了Matcher匹配物件的構建, 以及快速驗證給定字串是否和給定正則表示式模式匹配的功能.

Matcher 中的capturing group概念

首先Matcher 的概念是解釋Pattern. 我理解的是有時候我們使用正則表示式不僅僅是完成簡單的驗證字串是否和模式匹配, 而是需要更加靈活和高階的操作(比如獲取部分匹配成功的子字串功能), 此時就需要這個Matcher物件. Java中需要呼叫Pattern中的matcher方法返回這個物件, 而groovy中只需要使用=~ 操作符號即可建立這樣的物件.抽象的講, 這個物件即是儲存一個正則表示式模式與一個給定輸入字串的所有匹配相關的資訊. capturing group 這個概念是針對正則表示式中的() 引入的, 正則表示式中的括號表示group,捕獲組是從左往右計算其開始括號進行編號的(因為具有括號巢狀的情況, 括號層次越高那麼它的組編號自然越小), 其中0表示整個表示式, 如下例子:

(A (BC))
group 0: (A(BC))
group 1: (A(BC))
group 2: (BC)

計算表示式的group就從左括號開始算遇到一個左括號group number就加1. 使用group可以用於捕獲輸入字串與模式匹配上的部分對應group位置的子字串.

def str = "hello wrold hello"
def reg = /((el)(l))o/
def matcher = str=~reg
def num = 0
while(matcher.find()){
    println "the ${num} match sub sequenc"
    num++
    groupnum = matcher.groupCount()
    println "group count ${matcher.groupCount()}"
    println "group string ${matcher.group()}"
    println "group 0 string ${matcher.group(0)}"
    for(id in 1..groupnum){
        println "group ${id} string ${matcher.group(id)}"
        println "start index is ${matcher.start(id)} and end index is ${matcher.end(id)}"
    }
}

執行結果:

the 0 match sub sequenc
group count 3
group string ello
group 0 string ello
group 1 string ell
start index is 1 and end index is 4
group 2 string el
start index is 1 and end index is 3
group 3 string l
start index is 3 and end index is 4
the 1 match sub sequenc
group count 3
group string ello
group 0 string ello
group 1 string ell
start index is 13 and end index is 16
group 2 string el
start index is 13 and end index is 15
group 3 string l
start index is 15 and end index is 16

有一個點比較重要就是groupCount 組數量是不會將group 0計算在內的,組的數量是和括號數量保持一致, 其次是matcher.group() 方法和matcher.group(0) 方法返回內容都一樣, 都是和模式匹配的完成的子序列, 當傳遞引數時返回的就是相應編號的捕獲組獲取的子序列.當然可以使用start end等方法獲取到匹配的字串(或者捕獲組匹配到的字串)的偏移量(end的偏移量位置始終是最後一個字元的位置加1).

Matcher 重置

匹配器的重置涉及到兩個方法find()reset 其中find 方法可以指定從哪一個位置重新開始尋找模式匹配的字串.比如:

def reg = /el/
def str = "hello world hello"
def matcher = str=~reg
while(matcher.find()){
    println matcher.group()
}
matcher.find(0) // 重置matcher 從頭開始尋找匹配字串
// 但此時第一個匹配的子字串已經獲取到了,下一次呼叫find則是查詢下一個匹配字串
println "reset the matcher"
while(matcher.find()){
    println matcher.group()
}

結果:

el
el
reset the matcher
el

當然最好使用reset來完成這個過程:

def reg = /el/
def str = "hello world hello"
def matcher = str=~reg
while(matcher.find()){
    println matcher.group()
}
matcher.reset()
println "reset the matcher"
while(matcher.find()){
    println matcher.group()
}

輸出結果:

el
el
reset the matcher
el
el

小結

以上就是Groovy中的正則表示式一些比較常見的知識點, 主要是Groovy中特有的操作符號來構建正則表示式使用過程中依賴的各種物件. 最本質的還是迴歸到java中正則表示式相關的兩個核心類(Pattern和Matcher). Groovy有時候表現得更像是一種"語法糖", 以指令碼的方式來完成Java的程式設計.後續遇到更多跟Groovy正則相關的內容會持續更新本文.