正則捕獲組與非捕獲組

正則捕獲組與非捕獲組

正則表達式分組分爲捕獲組(Capturing Groups)與非捕獲組Non-Capturing Groups,那爲什麼需要分組呢?

在這裏插入圖片描述


正則表達式分組分爲捕獲組(Capturing Groups)與非捕獲組Non-Capturing Groups。正則裏面是用成對的小括號來表示分組的,如(\d)表示一個分組,(\d)(\d)表示有兩個分組,(\d)(\d)(\d)表示有三個分組,有幾對小括號元字符組成,就表示有幾個分組,以此類推。那爲什麼需要分組呢?分組的目的如下:

  1. 作爲可選分支
  2. 簡寫重複模式
  3. 緩存捕獲數據及反向引用(只有捕獲組纔可以被反向引用)

捕獲組

當你把一個正則表達式用一對小括號包起來的時候,就形成了一個捕獲組,既然是捕獲組,那它捕獲的是什麼呢?它會把括號裏面的正則表達式匹配到的內容保存到該分組裏面,也就是說,它捕獲的就是分組裏面的正則表達式匹配到的內容。而最簡單的捕獲組是(),只是它匹配的是一個空字符(即字符之前的位置,如下圖:)
在這裏插入圖片描述
接下來我們來看下,捕獲組是怎麼捕獲和分組的。比如要把一個日期格式爲2019-11-10的年月日分別匹配出來
很簡單,我們先來看下年月日的結構組成:4個數字後接一個連接符-,再接上2個數字後接連接符-,再接上2個數字。所以我們可以這麼寫 \d{4}-\d{2}-\d{2} ,匹配結果如下
在這裏插入圖片描述
可以看到,這個表達式匹配的是整個日期格式,並沒有把年月日的信息分開匹配。那我們怎麼寫才能把年月日的信息分開呢?很簡單,利用捕獲組。如下圖
在這裏插入圖片描述
可以看到,當我們在年月日對應的正則兩邊用小括號括起來的時候,陸續出現了Group1、Group2和Group3的信息了。既然數據可以分別獲取到了,後面就可以對這三個數據進行你想要的操作了。

這是捕獲組的第一個運用:用來拆分匹配到的數據。接下來我們再來看下捕獲組的第二個運用:反向引用。比如,現在有一個需求,要匹配如1212,3434,7979這類數據
我們先來分析一下數據結構:可以看出,後兩個數據是前兩個數據的重複,那就是ABAB這種模式,所以我們可以把AB先分組,然後在對其引用就可以了。即(\d{2})\1,把\d{2}匹配到的內容先保存到捕獲組1中,然後再對捕獲組1進行引用,當(\d{2})匹配到的是12的時候,\1就表示12,當(\d{2})匹配到的是34的時候,\1就是34。
在這裏插入圖片描述

正則裏面,引用捕獲組的語法是這樣的\number,其中number是大於等於1的正整數(PS:,group0表示整個正則表達式匹配到的內容,而捕獲組的編號是從1開始的,也就是\1,大多數正則引擎最大支持99個捕獲組,也就是\99)。我們知道,正則表達式是從字符串的左邊開始往右匹配的,且一般都是消耗掉已經被匹配的字符串的(環視語法不需要消耗字符串)。因此,當前面已經出現了捕獲組1、2、3等等的捕獲組,我們就可以在後面的正則裏面用\1、\2、\3等來進行相應的引用了。

非捕獲組

非捕獲組的語法是在捕獲組的基礎上,在左括號的右側加上?:就可以了,那就是(?:)。例如,(\d)表示捕獲組,而(?:\d)表示非捕獲組。既然是非捕獲組,那它就不會把正則匹配到的內容保存到分組裏面。
在這裏插入圖片描述
如上圖所示,當我們把捕獲組改爲非捕獲組的時候,那些分組的信息就不見了,這個時候,(?:\d{4})-(?:\d{2})-(?:\d{2})\d{4}-\d{2}-\d{2}匹配到的內容其實是一樣的。

那什麼時候需要用到非捕獲組呢?

先來看第一點:不需要用到分組裏面的內容的時候,用非捕獲組,主要是爲了提升效率,因爲捕獲組多了一步保存數據的步驟,所以一般會多耗費一些時間,雖然時間很短。
再來看第二點:用在可選分支的時候,當我們不需要分組裏面的數據的時候,也可以用非捕獲組,如果需要的話,則用捕獲組。來看下面一個例子:
在這裏插入圖片描述
雖然這個時候(red|blue|green) color也可以實現該功能,但一般情況下,爲了提高些許的性能,還是推薦用非捕獲組(?:red|blue|green) color,但這不是強制性的,只是個建議。

下面我們再來看一個問題,需要匹配一串以逗號隔開的數字字符串,如123,456,789,321,2345567,5678
但是逗號不能再最前面和最後面,且逗號不能連續相連出現。這個時候,簡單的辦法就要利用分組和量詞來配合了,如下:
在這裏插入圖片描述
^\d+(?:,\d+)*$這裏我們利用到了^來限制要以數字開頭,$來限制要以數字結尾,()用來分組,而量詞*用來對它前面的分組進行0或多次的重複匹配。

我們知道,^表示匹配開頭位置,而$表示匹配結尾的位置。但我們需要對一個字符串模式進行校驗的時候,一般需要在前後加上這兩個限制符號,以保證是從開始位置開始匹配的,到某處結尾,不然校驗的結果往往是不對的。比如校驗手機號碼、身份證號碼、密碼等等,就需要加上^$;而如果只是匹配數據的話,一般是不需要加上的,因爲我們要匹配的數據往往有可能在一大串字文本的任何位置上,加上了反而會適得其反。這裏只是提示一下,後面會有校驗的專題介紹。

命名捕獲組

捕獲組其實是分爲編號捕獲組Numbered Capturing Groups和命名捕獲組Named Capturing Groups的,我們上面說的捕獲組,默認指的是編號捕獲組。命名捕獲組,也是捕獲組,只是語法不一樣。命名捕獲組的語法如下:(?<name>group)(?'name'group),其中name表示捕獲組的名稱,group表示捕獲組裏面的正則。

命名捕獲組有什麼作用?
前面我們之間介紹了編號捕獲組了,爲什麼還需要命名捕獲組呢?其實命名捕獲組跟編號捕獲組比起來,唯一的優點是用命名捕獲組會比較直觀,可以用名稱描述捕獲到的內容的含義,因爲可以命名。
前面匹配年月日的正則
(\d{4})-(\d{2})-(\d{2})
我們現在可以把它改爲
(?'year'\d{4})-(?'month'\d{2})-(?'day'\d{2})

(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})
在這裏插入圖片描述
如上圖,編號捕獲組會把匹配到的年月日分別保存到group1、group2和group3中,也就是說,它是按從左到右給捕獲組編號的,然後捕獲到的內容分別放在對應的組裏面。
而命名捕獲組,是把分組匹配到的內容保存到對應名稱的分組裏面,如下兩種寫法的命名捕獲組,會把2019保存到名稱爲year的捕獲組裏面,把11保存到名稱爲month的捕獲組裏面,把10保存到名稱爲day的捕獲組裏面。
在這裏插入圖片描述
在這裏插入圖片描述
下面,我們再來思考幾個問題。

1、命名捕獲組能用?:改爲非捕獲組嗎?

答案是不行的,原因很簡單,命名捕獲組的作用本來就是爲了更明確地給捕獲組捕獲到的內容命名的,如果能用?:改爲非捕獲組,那就失去了它本來的價值了。

2、命名捕獲組是怎麼反向引用的?

我們可以用\k<name>\k'name'的形式來對前面的命名捕獲組捕獲到的值進行引用。如之前的
(\d{2})\1
可以改寫爲
(?<key>\d{2})\k<key>
其中,key是我們對捕獲組的命名,而\k<key>是對key這個分組捕獲到的內容進行引用,所謂的引用,就是利用它前面匹配到的字符串的值
在這裏插入圖片描述

3、一個正則裏,多個命名捕獲組能同名嗎?

這個問題,一般是不允許的,但有個別編程語言是允許的,例如java、python等是不允許的,而C#、Perl等是允許的。因此,最好不要這麼寫。

java是從JDK1.7開始纔開始支持命名捕獲組的。那編號捕獲組有存在編號重複的情況嗎?答案是沒有的,因爲編號捕獲組的編號是根據()去自動識別的,而命名分組是人爲命名的。另外,雖然命名捕獲組看起來比較明確捕獲到的內容是什麼,但其寫法也是比較繁瑣的,並且存在編程語言的兼容性問題,因此,能用編號捕獲組去解決的問題,就不需要用命名捕獲組了。

4、正則裏面,怎麼計算捕獲組的個數?

在正則裏面,計算整個表達式裏面有幾個捕獲組的規律如下:
從左到右,計算小左括號(的個數有幾個就有幾個捕獲組編號的順序也是按出現的先後順序去排序的。以下三種情況除外:
\( 被轉義過的括號不算
(?:) 非捕獲組的不算
[(] 放在方括號裏面的小括號也不算,這也屬於被轉義過。

(\d{4})-(\d{2})-(\d{2})幾個分組?有幾個捕獲組?每個捕獲組對應的編號是多少?
在這裏插入圖片描述
按照上面的規律,從左到右,有三個(,並且這上個左括號不是被排除掉的那三種情況之一的,所以我們可以確定,該正則有三個分組,並且都是捕獲組。由於(\d{4})所在的這個(是最先出現的,因此(\d{4})的編號是1,爲group1;接下來出現的是第一個(\d{2})(上面有兩個(\d{2})),所以這是編號爲2的捕獲組,group2;而最後出現(的位置是最後的那個(\d{2}),因此,這是編號爲3的捕獲組,即group3。由上圖的匹配結果詳情也可以看出來。

再來看下,這個正則表達式的分組情況:
(?:\d{4})-(\d{2})-(\d{1,2})
我們還是從左到右查找(的個數,由於第一個出現的((?:,所以它雖然是分組,但它是非捕獲組,因此(?:\d{4})不會有捕獲組的編號,第二個出現(的是(\d{2}),因此這個捕獲組的編號爲1,即group1,接下來出現(的是(\d{1,2}),因此此處捕獲組的編號爲2,即group2。
所以,該正則有三個分組,第一個爲非捕獲組,第二個爲捕獲組1,第三個爲捕獲組2。
現在,我們來看下用| 隔開的捕獲組的編號是否也是符合上面的計算捕獲組編號的規律。例如 (\d)|(\d)
在這裏插入圖片描述
由上圖可以看出,這個數字2是由group1分組匹配到的,但是右下方的匹配詳情裏卻沒有顯示group2的信息,是不是這種情況下只有一個分組呢?

其實不是的,這種情況也是有兩個分組,group1和group2,只是此時group2沒有匹配到數據,所以這個在線工具沒有顯示出來而已。其實你看下右上方的正則解釋,就清楚了。描述很清楚1st Capturing Group2st Capturing Group。因此(\d)|(\d)也是有兩個捕獲組的。下面我們用RegexBuddy工具查看匹配結果:
在這裏插入圖片描述
沒錯,就是有兩個捕獲組,只是此時group2不參與匹配而已

正則表達式分組的相關知識就介紹到這裏,希望對你有幫助。實踐出真知,想要深入學習,就得多學、多思、多實踐。

微信公衆號:Cooking Regex
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章