引言
本文將逐步討論一些正規表示式的使用話題。本文為本站基礎篇之後的擴充套件,在閱讀本文之前,建議先閱讀正規表示式參考文件一文。
1. 表示式的遞迴匹配
有時候,我們需要用正規表示式來分析乙個計算式中的括號配對情況。比如,使用表示式 "/( [^)]* /)" 或者 "/( .*? /)" 可以匹配一對小括號。但是如果括號 內還嵌有一層括號的話 ,如 "( ( ) )",則這種寫法將不能夠匹配正確,得到的結果是 "( ( )" 。類似情況的還有 html 中支援巢狀的標籤如 "" 等。本節將要討論的是,想辦法把有巢狀的的成對括號或者成對標籤匹配出來。
匹配未知層次的巢狀:
有的正規表示式引擎,專門針對這種巢狀提供了支援。並且在棧空間允許的情況下,能夠支援任意未知層次的巢狀:比如 perl,php,greta 等。在 php 和 greta 中,表示式中使用 "(?r)" 來表示巢狀部分。
匹配巢狀了未知層次的 "小括號對" 的表示式寫法如下:"/( ([^()] | (?r))* /)"。
[perl 和 php 的示例**]
匹配有限層次的巢狀:
對於不支援巢狀的正規表示式引擎,只能通過一定的辦法來匹配有限層次的巢狀。思路如下:
第一步,寫乙個不能支援巢狀的表示式:"/( [^()]* /)","((?!).)*
"。 這兩個表示式在匹配有巢狀的文字時,只匹配最內層。
第二步,寫乙個可匹配巢狀一層的表示式:"/( ([^()] | /( [^()]* /))* /)"。這個表示式在匹配巢狀層數大於一時,只能匹配最裡面的兩層,同時,這個表示式也能匹配沒有巢狀的文字或者巢狀的最裡層。
匹配巢狀一層的 "" 標籤,表示式為:"((?!).|(((?!).)*))*
"。這個表示式在匹配 "" 巢狀層數大於一的文字時,只匹配最裡面的兩層。
第三步,找到匹配巢狀(n)層的表示式 與 巢狀(n-1)層的表示式之間的關係。比如,能夠匹配巢狀(n)層的表示式為:
[標記頭] ( [匹配 [標記頭] 和 [標記尾] 之外的表示式] | [匹配 n-1 層的表示式] )* [標記尾]
回頭來看前面編寫的「可匹配巢狀一層」的表示式: /(
([^()]
|/(([^()])*/)
)*/)
((?!).
|(((?!).)*)
)*php 和 greta 的簡便之處在於,匹配巢狀(n-1)層的表示式用 (?r) 表示:/((
[^()]
|(?r)
)*/)
第四步,依此類推,可以編寫出匹配有限(n)層的表示式。這種方式寫出來的表示式,雖然看上去很長,但是這種表示式經過編譯後,匹配效率仍然是很高的。
2. 非貪婪匹配的效率
可能有不少的人和我一樣,有過這樣的經歷:當我們要匹配類似 "內容" 或者 "[b]加粗[/b]" 這樣的文字時,我們根據正向預搜尋功能寫出這樣的表示式:"([^<]|<(?!/td>))*
" 或者 "((?!).)*"。
當發現非貪婪匹配之時,恍然大悟,同樣功能的表示式可以寫得如此簡單:".*?
"。 頓時間如獲至寶,凡是按邊界匹配的地方,盡量使用簡捷的非貪婪匹配 ".*?"。特別是對於複雜的表示式來說,採用非貪婪匹配 ".*?" 寫出來的表示式的確是簡練了許多。
然而,當乙個表示式中,有多個非貪婪匹配時,或者多個未知匹配次數的表示式時,這個表示式將可能存在效率上的陷阱。有時候,匹配速度慢得莫名奇妙,甚至開始懷疑正規表示式是否實用。
效率陷阱的產生:
在本站基礎文章裡,對非貪婪匹配的描述中說到:「如果少匹配就會導致整個表示式匹配失敗的時候,與貪婪模式類似,非貪婪模式會最小限度的再匹配一些,以使整個表示式匹配成功。」
具體的匹配過程是這樣的:
"非貪婪部分" 先匹配最少次數,然後嘗試匹配 "右側的表示式"。
如果右側的表示式匹配成功,則整個表示式匹配結束。如果右側表示式匹配失敗,則 "非貪婪部分" 將增加匹配一次,然後再嘗試匹配 "右側的表示式"。
如果右側的表示式又匹配失敗,則 "非貪婪部分" 將再增加匹配一次。再嘗試匹配 "右側的表示式"。
依此類推,最後得到的結果是 "非貪婪部分" 以盡可能少的匹配次數,使整個表示式匹配成功。或者最終仍然匹配失敗。
當乙個表示式中有多個非貪婪匹配,以表示式 "d(/w+?)d(/w+?)z" 為例,對於第乙個括號中的 "/w+?" 來說,右邊的 "d(/w+?)z" 屬於它的 "右側的表示式",對於第二個括號中的 "/w+?" 來說,右邊的 "z" 屬於它的 "右側的表示式"。
當 "z" 匹配失敗時,第二個 "/w+?" 會 "增加匹配一次",再嘗試匹配 "z"。如果第二個 "/w+?" 無論怎樣 "增加匹配次數",直至整篇文字結束,"z" 都不能匹配,那麼表示 "d(/w+?)z" 匹配失敗,也就是說第乙個 "/w+?" 的 "右側" 匹配失敗。此時,第乙個 "/w+?" 會增加匹配一次,然後再進行 "d(/w+?)z" 的匹配。迴圈前面所講的過程,直至第乙個 "/w+?" 無論怎麼 "增加匹配次數",後邊的 "d(/w+?)z" 都不能匹配時,整個表示式才宣告匹配失敗。
其實,為了使整個表示式匹配成功,貪婪匹配也會適當的「讓出」已經匹配的字元。因此貪婪匹配也有類似的情況。當乙個表示式中有較多的未知匹配次數的表示式時,為了讓整個表示式匹配成功,各個貪婪或非貪婪的表示式都要進行嘗試減少或增加匹配次數,由此容易形成乙個大迴圈的嘗試,造成了很長的匹配時間。本文之所以稱之為「陷阱」,因為這種效率問題往往不易察覺。
舉例:"d(/w+?)d(/w+?)d(/w+?)z" 匹配 "ddddddddddd..." 時,將花費較長一段時間才能判斷出匹配失敗 。
效率陷阱的避免:
避免效率陷阱的原則是:避免「多重迴圈」的「嘗試匹配」。並不是說非貪婪匹配就是不好的,只是在運用非貪婪匹配的時候,需要注意避免過多「迴圈嘗試」的問題。
情況一:對於只有乙個非貪婪或者貪婪匹配的表示式來說,不存在效率陷阱。也就是說,要匹配類似 "內容 " 這樣的文字,表示式 "([^<]|<(?!/td>))*
" 和 "((?!).)*
" 和 ".*?
" 的效率是完全相同的。
情況二:如果乙個表示式中有多個未知匹配次數的表示式,應防止進行不必要的嘗試匹配。
比如,對表示式 "" 來說, 如果前面部分表示式在遇到 "" 卻匹配失敗,將導致第乙個 ".*?" 增加匹配次數再嘗試。而對於表示式真正目的,讓第乙個 ".*?" 增加匹配成「vbscript'>」是不對的,因此這種嘗試是不必要的嘗試。
因此,對依靠邊界來識別的表示式,不要讓未知匹配次數的部分跨過它的邊界。前面的表示式中,第乙個 ".*?" 應該改寫成 "[^']*"。後邊那個 ".*?" 的右邊再沒有未知匹配次數的表示式,因此這個非貪婪匹配沒有效率陷阱。於是,這個匹配指令碼塊的表示式,應該寫成:"" 更好。
正規表示式話題
引言 本文將逐步討論一些正規表示式的使用話題。本文為本站基礎篇之後的擴充套件,在閱讀本文之前,建議先閱讀正規表示式參考文件一文。1.表示式的遞迴匹配 有時候,我們需要用正規表示式來分析乙個計算式中的括號配對情況。比如,使用表示式 或者 可以匹配一對小括號。但是如果括號 內還嵌有一層括號的話 如 則這...
正規表示式高階話題
1.表示式的遞迴匹配 有時候,我們需要用正規表示式來分析乙個計算式中的括號配對情況。比如,使用表示式 或者 可以匹配一對小括號。但是如果括號 內還嵌有一層括號的話 如 則這種寫法將不能夠匹配正確,得到的結果是 類似情況的還有 html 中支援巢狀的標籤如 等。本節將要討論的是,想辦法把有巢狀的的成對...
正規表示式 正規表示式 總結
非負整數 d 正整數 0 9 1 9 0 9 非正整數 d 0 負整數 0 9 1 9 0 9 整數 d 非負浮點數 d d 正浮點數 0 9 0 9 1 9 0 9 0 9 1 9 0 9 0 9 0 9 1 9 0 9 非正浮點數 d d 0 0 負浮點數 正浮點數正則式 英文本串 a za z...