【轉載】理解正則表達式的貪婪與非貪婪模式

概述

貪婪與非貪婪模式影響的是被量詞修飾的子表達式的匹配行爲,貪婪模式在整個表達式匹配成功的前提下,儘量多地匹配,而非貪婪模式在整個表達式匹配成功的前提下,儘量少地匹配。非貪婪模式只被部分NFA引擎(肯定性有窮自動機)所支持。 正則表達式

屬於貪婪模式的量詞,也叫作匹配優先量詞,包括:spa

1 「{m,n}?」、「{m,}?」、「??」、「*?」、「+?」

從正則語法的角度來說,被匹配優先量詞修飾的子表達式使用的就是貪婪模式,如「(Expression)+」;被忽略優先量詞修飾的子表達式使用的就是非貪婪模式,如(Expression)+?。對於貪婪模式,各類文檔的叫法基本一致,可是對於非貪婪模式,有的叫懶惰模式或惰性模式,有的叫勉強模式。其實叫什麼無所謂,只要掌握原理和用法,可以運用自如就好。我的習慣使用貪婪與非貪婪的叫法,因此文中都會使用這種叫法進行介紹。.net

貪婪與非貪婪模式匹配原理

對於貪婪與非貪婪模式,能夠從應用和原理兩個角度進行理解,但若是想真正掌握,仍是要從匹配原理來理解。先從應用的角度,回答一下「什麼是貪婪非貪婪模式」。先看一個例子,源字符串:code

before<div>block one</div>middle<div>block two</div>after

正則表達式一:blog

1 <div>.*</div> 

匹配結果一:three

<div>block one</div>middle<div>block two</div>

正則表達式二:文檔

1 <div>.*?</div> 

匹配結果二:字符串

<div>block one</div>(這裏指的是一次匹配結果,因此沒包括<div>block two</div>

根據上面的例子,從匹配行爲上分析一下,什是貪婪與非貪婪模式。正則表達式一採用的是貪婪模式,在匹配到第一個</div>時已經可使整個表達式匹配成功,可是因爲採用的是貪婪模式,因此仍然要向右嘗試匹配,查看是否還有更長的能夠成功匹配的子串,匹配到第二個</div>後,向右再沒有能夠成功匹配的子串,匹配結束,匹配結果爲<div>block one</div>block two<div>block three</div>。固然,實際的匹配過程並非這樣的,後面的匹配原理會詳細介紹。僅從應用角度分析,能夠這樣認爲,貪婪模式,就是在整個表達式匹配成功的前提下,儘量多地匹配,也就是所謂的「貪婪」,通俗點講,就是看到想要的,有多少就匹配多少,除非再也沒有想要的了。正則表達式二採用的是非貪婪模式,在匹配到第一個</div>時使整個表達式匹配成功,因爲採用的是非貪婪模式,因此結束匹配,再也不向右嘗試,匹配結果爲<div>block one</div>。僅從應用角度分析,能夠這樣認爲,非貪婪模式,就是在整個表達式匹配成功的前提下,儘量少地匹配,也就是所謂的「非貪婪」,通俗點講,就是找到一個想要的撿起來就好了,至於還有沒有沒撿的就無論了。io

關於前提條件的說明。在上面從應用角度分析貪婪與非貪婪模式時,一直提到的一個前提條件就是「整個表達式匹配成功」,爲何要強調這個前提,咱們看下下面的例子。class

正則表達式三:

1 <div>.*</div>middle

匹配結果三:

<div>block one</div>middle

修飾「.」的仍然是匹配優先量詞「*」,因此這裏仍是貪婪模式,前面的<div>.*</div>仍然能夠匹配到<div>block one</div>middle<div>block two</div>,可是因爲後面的middle沒法匹配成功,這時<div>.*</div>必須讓出已匹配的middle<div>block two</div>,以使整個表達式匹配成功。這時整個表達式匹配的結果爲<div>block one</div>middle<div>.*</div>匹配的內容爲<div>block one</div>。能夠看到,在「整個表達式匹配成功」的前提下,貪婪模式才真正地影響着子表達式的匹配行爲,若是整個表達式匹配失敗,貪婪模式只會影響匹配過程,對匹配結果的影響無從談起。非貪婪模式也存在一樣的問題,來看下面的例子。 

正則表達式四:

1 <div>.*?</div>after

匹配結果四:

<div>block one</div>middle<div>block two</div>after 

這裏採用的是非貪婪模式,前面的<div>.*?</div>仍然是匹配到<div>block one</div>爲止,此時後面的after沒法匹配成功,要求<div>.*?</div>必須繼續向右嘗試匹配,直到匹配內容爲<div>block one</div>middle<div>block two</div>時,後面的after才能匹配成功,整個表達式匹配成功,匹配的內容爲<div>block one</div>middle<div>block two</div>after,其中<div>.*?</div>匹配的內容爲<div>block one</div>middle<div>block two</div>。能夠看到,在「整個表達式匹配成功」的前提下,非貪婪模式才真正的影響着子表達式的匹配行爲,若是整個表達式匹配失敗,非貪婪模式沒法影響子表達式的匹配行爲。

經過應用角度的分析,已基本瞭解了貪婪與非貪婪模式的特性,那麼在實際應用中,到底是選擇貪婪模式,仍是非貪婪模式呢,這要根據需求來肯定。對於一些簡單的需求,好比源字符爲before<div>block one</div>middle,那麼取得div標籤,使用貪婪與非貪婪模式均可以取得想要的結果,使用哪種或許關係不大。可是就上述例子來講,在實際應用中,通常一次只須要取得一個配對出現的div標籤,也就是非貪婪模式匹配到的內容,貪婪模式所匹配到的內容一般並非咱們所須要的。那爲何還要有貪婪模式的存在呢,從應用角度很難給出滿意的解答了,這就須要從匹配原理的角度去分析貪婪與非貪婪模式。

下面從匹配原理角度分析貪婪與非貪婪模式。若是想真正瞭解什麼是貪婪模式,什麼是非貪婪模式,分別在什麼狀況下使用,各自的效率如何,那就不能僅僅從應用角度分析,而要充分了解貪婪與非貪婪模式的匹配原理。NFA引擎匹配原理,這裏主要針對貪婪與非貪婪模式涉及到的匹配原理進行介紹。先看一下貪婪模式簡單的匹配過程。

採用源字符串:"Regex"
採用正則表達式:.*

 

來看一下匹配過程。首先由第一個「」」取得控制權,匹配位置0位的「」」,匹配成功,控制權交給「.*」。「.*」取得控制權後,因爲「*」是匹配優先量詞,在可匹配可不匹配的狀況下,優先嚐試匹配。從位置1處的「R」開始嘗試匹配,匹配成功,繼續向右匹配,匹配位置2處的「e」,匹配成功,繼續向右匹配,直到匹配到結尾的「」」,匹配成功,因爲此時已匹配到字符串的結尾,因此「.*」結束匹配,將控制權交給正則表達式最後的「」」。「」」取得控制權後,因爲已經在字符串結束位置,匹配失敗,向前查找可供回溯的狀態,控制權交給「.*」,由「.*」讓出一個字符,也就是字符串結尾處的「」」,再把控制權交給正則表達式最後的「」」,由「」」匹配字符串結尾處的「」」,匹配成功。此時整個正則表達式匹配成功,其中「.*」匹配的內容爲「Regex」,匹配過程當中進行了一次回溯。接下來看一下非貪婪模式簡單的匹配過程。

採用源字符串:"Regex"
採用正則表達式:.*?

 

看一下非貪婪模式的匹配過程。首先由第一個「」」取得控制權,匹配位置0位的「」」,匹配成功,控制權交給「.*?」。「.*?」取得控制權後,因爲「*?」是忽略優先量詞,在可匹配可不匹配的狀況下,優先嚐試不匹配,因爲「*」等價於「{0,}」,因此在忽略優先的狀況下,能夠不匹配任何內容。從位置1處嘗試忽略匹配,也就是不匹配任何內容,將控制權交給正則表達式最後的「」」。「」」取得控制權後,從位置1處嘗試匹配,由「」」匹配位置1處的「R」,匹配失敗,向前查找可供回溯的狀態,控制權交給「.*?」,由「.*?」吃進一個字符,匹配位置1處的「R」,再把控制權交給正則表達式最後的「」」。「」」取得控制權後,從位置2處嘗試匹配,由「」」匹配位置1處的「e」,匹配失敗,向前查找可供回溯的狀態,重複以上過程,直到由「.*?」匹配到「x」爲止,再把控制權交給正則表達式最後的「」」。「」」取得控制權後,從位置6處嘗試匹配,由「」」匹配字符串最後的「」」,匹配成功。 此時整個正則表達式匹配成功,其中「.*?」匹配的內容爲「Regex」,匹配過程當中進行了五次回溯。

經過匹配原理的分析,能夠看到,在匹配成功的狀況下,貪婪模式進行了更少的回溯,而回溯的過程,須要進行控制權的交接,讓出已匹配內容或匹配未匹配內容,並從新嘗試匹配,在很大程度上下降匹配效率,因此貪婪模式與非貪婪模式相比,存在匹配效率上的優點。上述例子中,僅僅是一個簡單的應用,讀者看到這裏時,是否會存在這樣的疑問,貪婪模式就必定比非貪婪模式匹配效率高嗎?答案是否認的。

轉自:https://blog.csdn.net/u014762221/article/details/68953155

相關文章
相關標籤/搜索