正则表达式的贪婪模式、非贪婪模式、占有模式
文章目錄
- 一、Greediness(貪婪型)
- (一)貪婪模式示例
- (二)貪婪模式的匹配過程(貪婪模式的回溯)
- 二、Reluctant(勉強型)
- (一)非貪婪模式示例
- (二)非貪婪模式的匹配過程
- 三、Possessive(占有型)
- (一)占有模式示例
- (二)占用模式的匹配過程
一、Greediness(貪婪型)
貪婪模式,最大匹配,匹配優先。
在 Greediness 的模式下,會盡量大范圍的匹配,直到匹配了整個內容,這時發現匹配不能成功時,開始回退縮小匹配范圍,直到匹配成功。
默認情況下,所有的限定符都是貪婪模式,表示盡可能多的去捕獲字符。
(一)貪婪模式示例
正則表達式:/<.+>/,這個正則表達式的含義:匹配以 < 為首,以 > 為尾,中間是 1 個或多個任意字符的字符串。
被查找的字符串:a<tr>aava </tr>abb
匹配的結果:<tr>aava </tr>
Java 代碼舉例如下:
String test = "a<tr>aava </tr>abb"; String reg = "<.+>"; System.out.println(test.replaceAll(reg, "###"));說明:上面的 Java 代碼會將字符串 a<tr>aava </tr>abb 中所有符合正則式的字符串替換為 ###,最后輸出結果為:a###abb。其實 <tr> 和</tr> 也符合正則式 /<.+>/,但是貪婪呀,因為被匹配的字符串還很長,所以會繼續匹配下去,直到捕獲到最多且匹配成功為止。
注意:正則表達式的限定符(量詞)?、+、* 以及區間限定符 {n,m} 默認都是貪婪模式。
再舉個例子:
String test = "foooood"; String reg ="o{1,}"; System.out.println(test.replaceAll(reg, "#"));上面的 Java 代碼會一次匹配全部的字符 o,什么意思?就是說整個正則表達式單次匹配成功中,會匹配盡可能多的字符,而 ooooo 是符合正則式 o{1,} 的,所以在單次匹配成功中就全部捕獲了,所以最后輸出的結果為:f#d。如果你想得到這樣的結果:f#####d,應該怎么寫表達式呢?那么表達式成功匹配一個 o 時,就算整個正則表達式匹配成功 1 次,那么成功匹配 5 個 o,就算整個正則式匹配成功了 5 次,這樣就會執行 5 次替換,最后 ooooo 就會替換成 #####,我們看下面的示例。
String test = "foooood"; String reg ="o{1,}?"; System.out.println(test.replaceAll(reg, "#"));在區間量詞后添加個問號 ?,就是非貪婪模式,那么匹配了一個 o,就算正則式成功匹配一次,然后正則式又重新從正則式的第一個子表達式對被匹配的字符串已經成功匹配的下個字符開始繼續匹配,直到無字符可匹配為止。
(二)貪婪模式的匹配過程(貪婪模式的回溯)
貪婪模式的正則表達式<.*>去匹配字符串"a<>aava</>abb",會匹配到"<>aava</>",下面說一下貪婪模式的具體匹配過程。
首先 <.*> 中的 < 獲得控制權,嘗試匹配字符串"a<>aava</>abb"中的第一個字符"a",匹配失敗。繼續嘗試匹配第二個字符"<",匹配成功,將控制權交給 .*。
.* 為貪婪模式,會盡可能多的匹配字符,會先去匹配第三個字符">",匹配成功,記錄下一個回溯狀態,繼續匹配第四個字符"a",直到匹配到字符串結尾的最后一個字符"b"。字符串后沒有字符了,.* 繼續匹配失敗,將控制權交給 >。
> 嘗試匹配字符串結尾失敗,向前查找可回溯狀態,控制權交給 .*,.* 讓出一個字符,也就是字符串結尾的"b",然后將控制權交給 >。> 嘗試匹配字符串結尾的"b",匹配失敗,再次向前查找可回溯狀態,將控制權交給 .*…
重復以上過程,直到 .* 讓出第十個字符">",> 匹配第十個字符">"成功,從而整個正則表達式匹配成功一次。< 繼續從第十一個字符"a"開始匹配,但是直到字符串結束都沒有匹配成功的,最終匹配結束。
可以看到,.* 會盡可能多的匹配字符,直到無法繼續匹配,才會將控制權交給接下來的表達式。當接下來的表達式匹配失敗后,.* 會讓出之前匹配的字符,直到整個正則表達式匹配成功。
總結:貪婪模式的回溯,會讓出已經匹配成功的字符給下一個正則式的表達式。吃太多了,別人沒得吃,一點點吐出去給別人吃~
二、Reluctant(勉強型)
也叫 Laziness,懶惰型,懶惰模式,勉強模式,非貪婪模式,最小匹配,忽略優先。
在 Reluctant 的模式下,就是在匹配成功的前提下,盡可能少的匹配字符。
在限定詞后增加 ?,則是非貪婪模式,表示盡可能少的去捕獲字符。
例如:??,*?,+?,{n,}?,{n,m}?
(一)非貪婪模式示例
被查找的字符串:a<tr>aava </tr>abb
你希望匹配結果為:<tr>,就要使用表達式:/<.+?>/,但是從被查找的字符串可以知道,有兩個地方匹配正則表達式,所以最終會匹配得到兩個結果:<tr> 和 </tr>。
Java 代碼舉例如下:
String test = "a<tr>aava </tr>abb"; String reg = "<.+>"; System.out.println(test.replaceAll(reg, "###"));上面的 Java 代碼會將字符串 a<tr>aava </tr>abb 中所有符合表達式的字符串替換為 ###,最后輸出結果為:a###aava ###abb。
(二)非貪婪模式的匹配過程
先舉個例子:
正則式 <.+?> 去匹配字符串 a<>aava</>abb,會成功匹配到誰呢?四個選項:
A.<>
B.</>
C.<>aava</>
D.匹配失敗
我相信很多人會選中選項 B,不過正確答案是 C。
想要理解為什么在非貪婪模式下,<.+?> 還是匹配了這么多字符,需要知道非貪婪模式下的匹配過程。
首先 <.+?> 中的 < 獲得控制權,匹配字符串中的第一個字符"a",匹配失敗。< 繼續嘗試匹配第二個字符"<",匹配成功。
接下來 .+? 獲得控制權,. 可以匹配除換行符之外的任意一個字符,+ 表示一個或多個,? 表示非貪婪模式,即在成功的前提下盡量少的匹配字符,所以 .+? 會先去匹配成功一次,然后立刻就會將控制權交給正則表達式中的下一個。如果是貪婪模式,.+ 是不會匹配成功就早早地讓出控制權的,而是會繼續匹配下去,直到匹配失敗了才把控制權讓給正則表達式的下一個字符。
所以 .+? 獲得控制權后,會先去匹配第三個字符">",成功后記錄下回溯狀態,立即將控制權交給表達式中的 >。> 獲得控制權后,會嘗試匹配第四個字符"a",匹配失敗,向前查找可回溯狀態,于是 .+? 重新取得控制權,.+? 記錄的回溯位置是第 3 個字符“>”,于是 .+? 會去匹配回溯位置的下一個字符,也就是第 4 個字符“a”,匹配成功后再次記錄下回溯狀態,然后再次將控制權交給 >。> 獲得控制權后,會嘗試匹配第五個字符"a",結果匹配失敗,再次向前查找可回溯狀態…
就這樣重復上述過程,直到 .+? 匹配到了第九個字符"/",將控制權交給 >。> 匹配第十個字符">",終于匹配成功了,從而整個正則表達式匹配成功一次,記錄下匹配結果。因為每個字符只能被正則匹配一次,所以 < 繼續從第十一個字符"a"開始匹配,但是直到字符串結束都沒有匹配成功的,最終匹配結束。
可以看到 .+? 會先盡可能少的匹配字符,優先將控制權交給接下來的表達式。但因為接下來的表達式一直匹配失敗,.+? 不得不繼續匹配字符。最終 .+? 的匹配內容是">aava</",并且進行了 6 次回溯。
總結:非貪婪模式的回溯,是被迫繼續匹配下一個字符,再讓出控制權。給別人吃,別人吃不了,只好自己吃掉。
三、Possessive(占有型)
占有模式,完全匹配模式。
在限定符后面添加 + 就成為“占有模式”,例如:?+、*+、++、{n,}+、{n,m}+
Possessive 模式與 Greediness 有一定的相似性,那就是都盡量匹配最大范圍的內容,直到內容結束,但與 Greediness 不同的是,完全匹配不再回退嘗試匹配更小的范圍,也就是“不回溯”。
(一)占有模式示例
String test = "a<tr>aava </tr>abb"; String test2 = "<tr>"; String reg = "<.++>"; System.out.println(test.replaceAll(reg, "###")); System.out.println(test2.replaceAll(reg, "###"));以上的代碼,輸出結果為:a<tr>aava </tr>abb 和 <tr>,也就是說字符串a<tr>aava </tr>abb 和 <tr> 都不符合正則表達式 <.++>,那么這個表達式到底是怎么匹配的呢?
(二)占用模式的匹配過程
接下來看不回溯的占有模式是怎么匹配的。
占有模式的正則表達式 <.*+> 去匹配字符串"a<>aava</>abb",會匹配失敗。具體的匹配過程如下。
前兩步與貪婪模式的 <.*> 匹配過程基本一致。
< 獲得控制權,嘗試匹配字符串"a<>aava</>abb"中的第一個字符"a",匹配失敗。繼續嘗試匹配第二個字符"<",匹配成功,將控制權交給 .*+。
.*+ 也會盡可能多的匹配字符。不同的是,占有模式下表達式并不會記錄回溯狀態。.*+ 匹配到字符串結尾字符"b"后,沒有字符了,于是繼續匹配失敗,將控制權交給 >。
> 獲得控制權后,嘗試匹配字符串結尾,結尾無字符,匹配失敗。因為沒有回溯狀態可查找,> 直接匹配失敗,從而整個正則表達式 <.*+> 匹配失敗。
帶區間的類似 .{m,n}+ 也是不帶回溯狀態的,需要注意,具體匹配過程類似上文,就不詳述了。
總結:把東西全部吃掉了,也不吐出來,結果導致別人沒得吃
總結
以上是生活随笔為你收集整理的正则表达式的贪婪模式、非贪婪模式、占有模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于 vim 的 magic 设置
- 下一篇: 正则表达式实例解读