正则基础之 NFA引擎匹配原理
?來源:http://www.jb51.net/article/19332.htm
?
1 為什么要了解引擎匹配原理
一個(gè)個(gè)音符雜亂無章的組合在一起,彈奏出的或許就是噪音,同樣的音符經(jīng)過作曲家的手,就可以譜出非常動(dòng)聽的樂曲,一個(gè)演奏者同樣可以照著樂譜奏出動(dòng)聽的樂曲,但他/她或許不知道該如何去改變音符的組合,使得樂曲更動(dòng)聽。
作為正則 的使用者也一樣,不懂正則 引擎原理的情況下,同樣可以寫出滿足需求的正則 ,但是不知道原理,卻很難寫出高效且沒有隱患的正則 。所以對于經(jīng)常使用正則 ,或是有興趣深入學(xué)習(xí)正則 的人,還是有必要了解一下正則 引擎的匹配原理的。
2 正則 表達(dá)式引擎
正則 引擎大體上可分為不同的兩類:DFA和NFA,而NFA又基本上可以分為傳統(tǒng)型NFA和POSIX NFA。
DFA Deterministic finite automaton 確定型有窮自動(dòng)機(jī)
NFA Non-deterministic finite automaton 非確定型有窮自動(dòng)機(jī)
Traditional NFA
POSIX NFA
DFA引擎因?yàn)椴恍枰厮?#xff0c;所以匹配快速,但不支持捕獲組,所以也就不支持反向引用和$number這種引用方式,目前使用DFA引擎的語言和工具主要有awk、egrep 和 lex。
POSIX NFA主要指符合POSIX標(biāo)準(zhǔn)的NFA引擎,它的特點(diǎn)主要是提供longest-leftmost匹配,也就是在找到最左側(cè)最長匹配之前,它將繼續(xù)回溯。同DFA一樣,非貪婪模式或者說忽略優(yōu)先量詞對于POSIX NFA同樣是沒有意義的。
大多數(shù)語言和工具使用的是傳統(tǒng)型的NFA引擎,它有一些DFA不支持的特性:
捕獲組、反向引用和$number引用方式;
環(huán)視(Lookaround,(?<=…)、(?<!…)、(?=…)、(?!…)),或者有的有文章叫做預(yù)搜索;
忽略優(yōu)化量詞(??、*?、+?、{m,n}?、{m,}?),或者有的文章叫做非貪婪模式;
占有優(yōu)先量詞(?+、*+、++、{m,n}+、{m,}+,目前僅Java和PCRE支持),固化分組(?>…)。
引擎間的區(qū)別不是本文的重點(diǎn),僅做簡要的介紹,有興趣的可參考相關(guān)文獻(xiàn)。
3 預(yù)備知識(shí)
3.1 字符串組成
對于字符串“abc ”而言,包括三個(gè)字符和四個(gè)位置。
3.2 占有字符和零寬度
正則 表達(dá)式匹配過程中,如果子表達(dá)式匹配到的是字符內(nèi)容,而非位置,并被保存到最終的匹配結(jié)果中,那么就認(rèn)為這個(gè)子表達(dá)式是占有字符的;如果子表達(dá)式匹配的僅僅是位置,或者匹配的內(nèi)容并不保存到最終的匹配結(jié)果中,那么就認(rèn)為這個(gè)子表達(dá)式是零寬度的。
占有字符是互斥的,零寬度是非互斥的。也就是一個(gè)字符,同一時(shí)間只能由一個(gè)子表達(dá)式匹配,而一個(gè)位置,卻可以同時(shí)由多個(gè)零寬度的子表達(dá)式匹配。
3.3 控制權(quán)和傳動(dòng)
正則 的匹配過程,通常情況下都是由一個(gè)子表達(dá)式(可能為一個(gè)普通字符、元字符或元字符序列組成)取得控制權(quán),從字符串的某一位置開始嘗試匹配,一個(gè)子表達(dá)式開始嘗試匹配的位置,是從前一子表達(dá)匹配成功的結(jié)束位置開始的。如正則 表達(dá)式:
( 子表達(dá)式一)(子表達(dá)式二)
假設(shè)(子表達(dá)式一) 為零寬度表達(dá)式,由于它匹配開始和結(jié)束的位置是同一個(gè),如位置0,那么(子表達(dá)式二) 是從位置0開始嘗試匹配的。
假設(shè)(子表達(dá)式一) 為占有字符的表達(dá)式,由于它匹配開始和結(jié)束的位置不是同一個(gè),如匹配成功開始于位置0,結(jié)束于位置2,那么(子表達(dá)式二) 是從位置2開始嘗試匹配的。
而對于整個(gè)表達(dá)式來說,通常是由字符串位置0開始嘗試匹配的。如果在位置0開始的嘗試,匹配到字符串某一位置時(shí)整個(gè)表達(dá)式匹配失敗,那么引擎會(huì)使正則 向前傳動(dòng),整個(gè)表達(dá)式從位置1開始重新嘗試匹配,依此類推,直到報(bào)告匹配成功或嘗試到最后一個(gè)位置后報(bào)告匹配失敗。
4 正則 表達(dá)式簡單匹本過程
4.1 基礎(chǔ)匹配過程
?
源字符串:abc
正則 表達(dá)式:abc
匹配過程:
首先由字符“a ”取得控制權(quán),從位置0開始匹配,由“a ”來匹配“a ”,匹配成功,控制權(quán)交給字符“b ”;由于“a ”已被“a ”匹配,所以“b ”從位置1開始嘗試匹配,由“b ”來匹配“b ”,匹配成功,控制權(quán)交給“c ”;由“c ”來匹配“c ”,匹配成功。
此時(shí)正則 表達(dá)式匹配完成,報(bào)告匹配成功。匹配結(jié)果為“abc ”,開始位置為0,結(jié)束位置為3。
?
4.2 含有匹配優(yōu)先量詞的匹配過程——匹配成功(一)
源字符串:abc
正則 表達(dá)式:ab?c
量詞“? ”屬于匹配優(yōu)先量詞,在可匹配可不匹配時(shí),會(huì)先選擇嘗試匹配,只有這種選擇會(huì)使整個(gè)表達(dá)式無法匹配成功時(shí),才會(huì)嘗試讓出匹配到的內(nèi)容。這里的量詞“? ”是用來修飾字符“b ”的,所以“b? ”是一個(gè)整體。
匹配過程:
首先由字符“a ”取得控制權(quán),從位置0開始匹配,由“a ”來匹配“a ”,匹配成功,控制權(quán)交給字符“b? ”;由于“? ”是匹配優(yōu)先量詞,所以會(huì)先嘗試進(jìn)行匹配,由“b? ”來匹配“b ”,匹配成功,控制權(quán)交給“c ”,同時(shí)記錄一個(gè)備選狀態(tài);由“c ”來匹配“c ”,匹配成功。記錄的備選狀態(tài)丟棄。
此時(shí)正則 表達(dá)式匹配完成,報(bào)告匹配成功。匹配結(jié)果為“abc ”,開始位置為0,結(jié)束位置為3。
4.3 含有匹配優(yōu)先量詞的匹配過程——匹配成功(二)
源字符串:ac
正則 表達(dá)式:ab?c
匹配過程:
首先由字符“a ”取得控制權(quán),從位置0開始匹配,由“a ”來匹配“a ”,匹配成功,控制權(quán)交給字符“b? ”;先嘗試進(jìn)行匹配,由“b? ”來匹配“c ”,同時(shí)記錄一個(gè)備選狀態(tài),匹配失敗,此時(shí)進(jìn)行回溯,找到備選狀態(tài),“b? ”忽略匹配,讓出控制權(quán),把控制權(quán)交給“c ”;由“c ”來匹配“c ”,匹配成功。
此時(shí)正則 表達(dá)式匹配完成,報(bào)告匹配成功。匹配結(jié)果為“ac ”,開始位置為0,結(jié)束位置為2。其中“b? ”不匹配任何內(nèi)容。
4.4 含有匹配優(yōu)先量詞的匹配過程——匹配失敗
源字符串:abd
正則 表達(dá)式:ab?c
匹配過程:
首先由字符“a ”取得控制權(quán),從位置0開始匹配,由“a ”來匹配“a ”,匹配成功,控制權(quán)交給字符“b? ”;先嘗試進(jìn)行匹配,由“b? ”來匹配“b ”,同時(shí)記錄一個(gè)備選狀態(tài),匹配成功,控制權(quán)交給“c ”;由“c ”來匹配“d ”,匹配失敗,此時(shí)進(jìn)行回溯,找到記錄的備選狀態(tài),“b? ”忽略匹配,即“b? ”不匹配“b ”,讓出控制權(quán),把控制權(quán)交給“c ”;由“c ”來匹配“b ”,匹配失敗。此時(shí)第一輪匹配嘗試失敗。
正則 引擎使正則 向前傳動(dòng),由位置1開始嘗試匹配,由“a ”來匹配“b ”,匹配失敗,沒有備選狀態(tài),第二輪匹配嘗試失敗。
繼續(xù)向前傳動(dòng),直到在位置3嘗試匹配失敗,匹配結(jié)束。此時(shí)報(bào)告整個(gè)表達(dá)式匹配失敗。
4.5 含有忽略優(yōu)先量詞的匹配過程——匹配成功
源字符串:abc
正則 表達(dá)式:ab??c
量詞“?? ”屬于忽略優(yōu)先量詞,在可匹配可不匹配時(shí),會(huì)先選擇不匹配,只有這種選擇會(huì)使整個(gè)表達(dá)式無法匹配成功時(shí),才會(huì)嘗試進(jìn)行匹配。這里的量詞“?? ”是用來修飾字符“b ”的,所以“b?? ”是一個(gè)整體。
匹配過程:
首先由字符“a ”取得控制權(quán),從位置0開始匹配,由“a ”來匹配“a ”,匹配成功,控制權(quán)交給字符“b?? ”;先嘗試忽略匹配,即“b?? ”不進(jìn)行匹配,同時(shí)記錄一個(gè)備選狀態(tài),控制權(quán)交給“c ”;由“c ”來匹配“b ”,匹配失敗,此時(shí)進(jìn)行回溯,找到記錄的備選狀態(tài),“b?? ”嘗試匹配,即“b?? ”來匹配“b ”,匹配成功,把控制權(quán)交給“c ”;由“c ”來匹配“c ”,匹配成功。
此時(shí)正則 表達(dá)式匹配完成,報(bào)告匹配成功。匹配結(jié)果為“abc ”,開始位置為0,結(jié)束位置為3。其中“b?? ”匹配字符“b ”。
4.6 零寬度匹配過程
源字符串:a12
正則 表達(dá)式:^ (?=[a-z]) [a-z0-9]+ $
元字符“^ ”和“$ ”匹配的只是位置,順序環(huán)視“(?=[a-z]) ”只進(jìn)行匹配,并不占有字符,也不將匹配的內(nèi)容保存到最終的匹配結(jié)果,所以都是零寬度的。
這個(gè)正則 的意義就是匹配由字母和數(shù)字組成的,第一個(gè)字符是字母的字符串。
匹配過程:
首先由元字符“^ ”取得控制權(quán),從位置0開始匹配,“^ ”匹配的就是開始位置“位置0 ”,匹配成功,控制權(quán)交給順序環(huán)視“(?=[a-z]) ”;
“(?=[a-z]) ”要求它所在位置右側(cè)必須是字母才能匹配成功,零寬度的子表達(dá)式之間是不互斥的,即同一個(gè)位置可以同時(shí)由多個(gè)零寬度子表達(dá)式匹配,所以它也是從位置0嘗試進(jìn)行匹配,位置0的右側(cè)是字符“a ”,符合要求,匹配成功,控制權(quán)交給“[a-z0-9]+ ”;
因?yàn)椤?strong>(?=[a-z]) ”只進(jìn)行匹配,并不將匹配到的內(nèi)容保存到最后結(jié)果,并且“(?=[a-z]) ”匹配成功的位置是位置0,所以“[a-z0-9]+ ”也是從位置0開始嘗試匹配的,“[a-z0-9]+ ”首先嘗試匹配“a ”,匹配成功,繼續(xù)嘗試匹配,可以成功匹配接下來的“1 ”和“2 ”,此時(shí)已經(jīng)匹配到位置3,位置3的右側(cè)已沒有字符,這時(shí)會(huì)把控制權(quán)交給“$ ”;
元字符“$ ”從位置3開始嘗試匹配,它匹配的是結(jié)束位置,也就是“位置3 ”,匹配成功。
此時(shí)正則 表達(dá)式匹配完成,報(bào)告匹配成功。匹配結(jié)果為“a12 ”,開始位置為0,結(jié)束位置為3。其中“^ ”匹配位置0,“(?=[a-z]) ”匹配位置0,“[a-z0-9]+ ”匹配字符串“a12 ”,“$ ”匹配位置3。
總結(jié)
以上是生活随笔為你收集整理的正则基础之 NFA引擎匹配原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 标签的使用
- 下一篇: request.getParameter