NFA引擎匹配原理

1       爲何要了解引擎匹配原理

一個個音符雜亂無章的組合在一塊兒,彈奏出的或許就是噪音,一樣的音符通過做曲家的手,就能夠譜出很是動聽的樂曲,一個演奏者一樣能夠照着曲譜奏出動聽的樂曲,但他/她或許不知道該如何去改變音符的組合,使得樂曲更動聽。正則表達式

做爲正則的使用者也同樣,不懂正則引擎原理的狀況下,一樣能夠寫出知足需求的正則,可是不知道原理,卻很難寫出高效且沒有隱患的正則。因此對於常常使用正則,或是有興趣深刻學習正則的人,仍是有必要了解一下正則引擎的匹配原理的。工具

2       正則表達式引擎

正則引擎大致上可分爲不一樣的兩類:DFA和NFA,而NFA又基本上能夠分爲傳統型NFA和POSIX NFA。學習

DFA Deterministic finite automaton 肯定型有窮自動機優化

NFA Non-deterministic finite automaton 非肯定型有窮自動機spa

Traditional NFA.net

POSIX NFAblog

DFA引擎由於不須要回溯,因此匹配快速,但不支持捕獲組,因此也就不支持反向引用和$number這種引用方式,目前使用DFA引擎的語言和工具主要有awk、egrep 和 lex。字符串

POSIX NFA主要指符合POSIX標準的NFA引擎,它的特色主要是提供longest-leftmost匹配,也就是在找到最左側最長匹配以前,它將繼續回溯。同DFA同樣,非貪婪模式或者說忽略優先量詞對於POSIX NFA一樣是沒有意義的。get

大多數語言和工具使用的是傳統型的NFA引擎,它有一些DFA不支持的特性:it

  捕獲組、反向引用和$number引用方式;

  環視(Lookaround,(?<=…)、(?<!…)、(?=…)、(?!…)),或者有的有文章叫作預搜索;

  忽略優化量詞(??、*?、+?、{m,n}?、{m,}?),或者有的文章叫作非貪婪模式;

  佔有優先量詞(?+、*+、++、{m,n}+、{m,}+,目前僅Java和PCRE支持),固化分組(?>…)。

引擎間的區別不是本文的重點,僅作簡要的介紹,有興趣的可參考相關文獻。

3       預備知識

3.1     字符串組成

 

 

對於字符串「abc」而言,包括三個字符和四個位置。

3.2     佔有字符和零寬度

正則表達式匹配過程當中,若是子表達式匹配到的是字符內容,而非位置,並被保存到最終的匹配結果中,那麼就認爲這個子表達式是佔有字符的;若是子表達式匹配的僅僅是位置,或者匹配的內容並不保存到最終的匹配結果中,那麼就認爲這個子表達式是零寬度的。

佔有字符是互斥的,零寬度是非互斥的。也就是一個字符,同一時間只能由一個子表達式匹配,而一個位置,卻能夠同時由多個零寬度的子表達式匹配。

3.3     控制權和傳動

正則的匹配過程,一般狀況下都是由一個子表達式(可能爲一個普通字符、元字符或元字符序列組成)取得控制權,從字符串的某一位置開始嘗試匹配,一個子表達式開始嘗試匹配的位置,是從前一子表達匹配成功的結束位置開始的。如正則表達式:

(子表達式一)(子表達式二)

假設(子表達式一)爲零寬度表達式,因爲它匹配開始和結束的位置是同一個,如位置0,那麼(子表達式二)是從位置0開始嘗試匹配的。

假設(子表達式一)爲佔有字符的表達式,因爲它匹配開始和結束的位置不是同一個,如匹配成功開始於位置0,結束於位置2,那麼(子表達式二)是從位置2開始嘗試匹配的。

而對於整個表達式來講,一般是由字符串位置0開始嘗試匹配的。若是在位置0開始的嘗試,匹配到字符串某一位置時整個表達式匹配失敗,那麼引擎會使正則向前傳動,整個表達式從位置1開始從新嘗試匹配,依此類推,直到報告匹配成功或嘗試到最後一個位置後報告匹配失敗。

4       正則表達式簡單匹本過程

4.1     基礎匹配過程

 

 

 

源字符串:abc

正則表達式:abc

匹配過程:

首先由字符「a」取得控制權,從位置0開始匹配,由「a」來匹配「a」,匹配成功,控制權交給字符「b」;因爲「a」已被「a」匹配,因此「b」從位置1開始嘗試匹配,由「b」來匹配「b」,匹配成功,控制權交給「c」;由「c」來匹配「c」,匹配成功。

此時正則表達式匹配完成,報告匹配成功。匹配結果爲「abc」,開始位置爲0,結束位置爲3。

 

4.2     含有匹配優先量詞的匹配過程——匹配成功(一)

 

 

源字符串:abc

正則表達式:ab?c

量詞「?」屬於匹配優先量詞,在可匹配可不匹配時,會先選擇嘗試匹配,只有這種選擇會使整個表達式沒法匹配成功時,纔會嘗試讓出匹配到的內容。這裏的量詞「?」是用來修飾字符「b」的,因此「b?」是一個總體。

匹配過程:

首先由字符「a」取得控制權,從位置0開始匹配,由「a」來匹配「a」,匹配成功,控制權交給字符「b?」;因爲「?」是匹配優先量詞,因此會先嚐試進行匹配,由「b?」來匹配「b」,匹配成功,控制權交給「c」,同時記錄一個備選狀態;由「c」來匹配「c」,匹配成功。記錄的備選狀態丟棄。

此時正則表達式匹配完成,報告匹配成功。匹配結果爲「abc」,開始位置爲0,結束位置爲3。

4.3     含有匹配優先量詞的匹配過程——匹配成功(二)

 

 

源字符串:ac

正則表達式:ab?c

匹配過程:

首先由字符「a」取得控制權,從位置0開始匹配,由「a」來匹配「a」,匹配成功,控制權交給字符「b?」;先嚐試進行匹配,由「b?」來匹配「c」,同時記錄一個備選狀態,匹配失敗,此時進行回溯,找到備選狀態,「b?」忽略匹配,讓出控制權,把控制權交給「c」;由「c」來匹配「c」,匹配成功。

此時正則表達式匹配完成,報告匹配成功。匹配結果爲「ac」,開始位置爲0,結束位置爲2。其中「b?」不匹配任何內容。

4.4     含有匹配優先量詞的匹配過程——匹配失敗

 

 

源字符串:abd

正則表達式:ab?c

匹配過程:

首先由字符「a」取得控制權,從位置0開始匹配,由「a」來匹配「a」,匹配成功,控制權交給字符「b?」;先嚐試進行匹配,由「b?」來匹配「b」,同時記錄一個備選狀態,匹配成功,控制權交給「c」;由「c」來匹配「d」,匹配失敗,此時進行回溯,找到記錄的備選狀態,「b?」忽略匹配,即「b?」不匹配「b」,讓出控制權,把控制權交給「c」;由「c」來匹配「b」,匹配失敗。此時第一輪匹配嘗試失敗。

正則引擎使正則向前傳動,由位置1開始嘗試匹配,由「a」來匹配「b」,匹配失敗,沒有備選狀態,第二輪匹配嘗試失敗。

繼續向前傳動,直到在位置3嘗試匹配失敗,匹配結束。此時報告整個表達式匹配失敗。

4.5     含有忽略優先量詞的匹配過程——匹配成功

 

 

源字符串:abc

正則表達式:ab??c

量詞「??」屬於忽略優先量詞,在可匹配可不匹配時,會先選擇不匹配,只有這種選擇會使整個表達式沒法匹配成功時,纔會嘗試進行匹配。這裏的量詞「??」是用來修飾字符「b」的,因此「b??」是一個總體。

匹配過程:

首先由字符「a」取得控制權,從位置0開始匹配,由「a」來匹配「a」,匹配成功,控制權交給字符「b??」;先嚐試忽略匹配,即「b??」不進行匹配,同時記錄一個備選狀態,控制權交給「c」;由「c」來匹配「b」,匹配失敗,此時進行回溯,找到記錄的備選狀態,「b??」嘗試匹配,即「b??」來匹配「b」,匹配成功,把控制權交給「c」;由「c」來匹配「c」,匹配成功。

此時正則表達式匹配完成,報告匹配成功。匹配結果爲「abc」,開始位置爲0,結束位置爲3。其中「b??」匹配字符「b」。

4.6     零寬度匹配過程

源字符串:a12

正則表達式:^(?=[a-z])[a-z0-9]+$

元字符「^」和「$」匹配的只是位置,順序環視「(?=[a-z])」只進行匹配,並不佔有字符,也不將匹配的內容保存到最終的匹配結果,因此都是零寬度的。

這個正則的意義就是匹配由字母或數字組成的,第一個字符是字母的字符串。

匹配過程:

首先由元字符「^」取得控制權,從位置0開始匹配,「^」匹配的就是開始位置「位置0」,匹配成功,控制權交給順序環視「(?=[a-z])」;

(?=[a-z])」要求它所在位置右側必須是字母才能匹配成功,零寬度的子表達式之間是不互斥的,即同一個位置能夠同時由多個零寬度子表達式匹配,因此它也是從位置0嘗試進行匹配,位置0的右側是字符「a」,符合要求,匹配成功,控制權交給「[a-z0-9]+」;

由於「(?=[a-z])」只進行匹配,並不將匹配到的內容保存到最後結果,而且「(?=[a-z])」匹配成功的位置是位置0,因此「[a-z0-9]+」也是從位置0開始嘗試匹配的,「[a-z0-9]+」首先嚐試匹配「a」,匹配成功,繼續嘗試匹配,能夠成功匹配接下來的「1」和「2」,此時已經匹配到位置3,位置3的右側已沒有字符,這時會把控制權交給「$」;

元字符「$」從位置3開始嘗試匹配,它匹配的是結束位置,也就是「位置3」,匹配成功。

此時正則表達式匹配完成,報告匹配成功。匹配結果爲「a12」,開始位置爲0,結束位置爲3。其中「^」匹配位置0,「(?=[a-z])」匹配位置0,「[a-z0-9]+」匹配字符串「a12」,「$」匹配位置3。

 

 

 NFA引擎匹配原理 來源:http://blog.csdn.net/lxcnn/article/details/4304651

相關文章
相關標籤/搜索