環(huán)視只進行子表達式的匹配,不占有字符,匹配到的內(nèi)容不保存到最終的匹配結(jié)果,是零寬度的。環(huán)視匹配的最終結(jié)果就是一個位置。
環(huán)視的作用相當(dāng)于對所在位置加了一個附加條件,只有滿足這個條件,環(huán)視子表達式才能匹配成功。
環(huán)視按照方向劃分有順序和逆序兩種,按照是否匹配有肯定和否定兩種,組合起來就有四種環(huán)視。順序環(huán)視相當(dāng)于在當(dāng)前位置右側(cè)附加一個條件,而逆序環(huán)視相當(dāng)于在當(dāng)前位置左側(cè)附加一個條件。
表達式 |
說明 |
(?<=Expression) |
逆序肯定環(huán)視,表示所在位置左側(cè)能夠匹配 Expression |
(?<!Expression) |
逆序否定環(huán)視,表示所在位置左側(cè)不能匹配 Expression |
(?=Expression) |
順序肯定環(huán)視,表示所在位置右側(cè)能夠匹配 Expression |
(?!Expression) |
順序否定環(huán)視,表示所在位置右側(cè)不能匹配 Expression |
? 對于環(huán)視的叫法,有的文檔里叫預(yù)搜索,有的叫什么什么斷言的,這里使用了更多人容易接受的《精通正則表達式》中 “ 環(huán)視 ” 的叫法,其實叫什么無所謂,只要知道是什么作用就是了,就這么幾個語法規(guī)則, ? 還是很容易記的
2 ?????? 環(huán)視匹配原理
? 環(huán)視是正則中的一個難點,對于環(huán)視的理解,可以從應(yīng)用和原理兩個角度理解,如果想理解得更清晰、深入一些,還是從原理的角度理解好一些,正則匹配基本原理參考 NFA 引擎匹配原理 。
上面提到環(huán)視相當(dāng)于對“所在位置”附加了一個條件,環(huán)視的難點在于找到這個“位置”,這一點解決了,環(huán)視也就沒什么秘密可言了。
順序環(huán)視匹配過程
對于順序肯定環(huán)視 (?=Expression) 來說,當(dāng)子表達式 Expression 匹配成功時, (?=Expression) 匹配成功,并報告 (?=Expression) 匹配當(dāng)前位置成功。
對于順序否定環(huán)視 (?!Expression) 來說,當(dāng)子表達式 Expression 匹配成功時, (?!Expression) 匹配失?。划?dāng)子表達式 Expression 匹配失敗時, (?!Expression) 匹配成功,并報告 (?!Expression) 匹配當(dāng)前位置成功;
順序肯定環(huán)視的例子已在 NFA 引擎匹配原理 中講解過了,這里再講解一下順序否定環(huán)視。
?
源字符串: aa<p>one</p>bb< div >two</div>cc
正則表達式: < (?!/?p/b) [^>]+ >
這個正則的意義就是匹配除 <p…> 或 </p> 之外的其余標(biāo)簽。
匹配過程:
?
首先由字符 “ < ” 取得控制權(quán),從位置 0 開始匹配,由于 “ < ” 匹配 “ a ” 失敗,在位置 0 處整個表達式匹配失敗,第一次迭代匹配失敗,正則引擎向前傳動,由位置 1 處開始嘗試第二次迭代匹配。
重復(fù)以上過程,直到位置 2 , “ < ” 匹配 “ < ” 成功,控制權(quán)交給“ (?!/?p/b) ”;“ (?!/?p/b) ”子表達式取得控制權(quán)后,進行內(nèi)部子表達式的匹配。首先由“ /? ”取得控制權(quán),嘗試匹配“ p ”失敗,進行回溯,不匹配,控制權(quán)交給“ p ”;由“ p ”來嘗試匹配“ p ”,匹配成功,控制權(quán)交給“ /b ”;由“ /b ”來嘗試匹配位置 4 ,匹配成功。此時子表達式匹配完成,“ /?p/b ”匹配成功,那么環(huán)視表達式“ (?!/?p/b) ”就匹配失敗。在位置 2 處整個表達式匹配失敗,新一輪迭代匹配失敗,正則引擎向前傳動,由位置 3 處開始嘗試下一輪迭代匹配。
在位置 8 處也會遇到一輪“ /?p/b ”匹配“ /p ”成功,而導(dǎo)致環(huán)視表達式“ (?!/?p/b) ”匹配失敗,從而導(dǎo)致整個表達式匹配失敗的過程。
重復(fù)以上過程,直到位置 14 , “ < ” 匹配 “ < ” 成功,控制權(quán)交給“ (?!/?p/b) ”;“ /? ”嘗試匹配“ d ”失敗,進行回溯,不匹配,控制權(quán)交給“ p ”;由“ p ”來嘗試匹配“ d ”,匹配失敗,已經(jīng)沒有備選狀態(tài)可供回溯,匹配失敗。此時子表達式匹配完成,“ /?p/b ”匹配失敗,那么環(huán)視表達式“ (?!/?p/b) ”就匹配成功。匹配的結(jié)果是位置 15 ,然后控制權(quán)交給“ [^>]+ ”;由“ [^>]+ ”從位置 15 進行嘗試匹配,可以成功匹配到“ div ”,控制權(quán)交給“ > ”;由“ > ”來匹配“ > ”。
此時正則表達式匹配完成,報告匹配成功。匹配結(jié)果為 “ <div> ” ,開始位置為 14 ,結(jié)束位置為 19 。其中“ < ”匹配“ < ”,“ (?!/?p/b) ”匹配位置 15 ,“ [^>]+ ”匹配字符串“ div ”,“ > ”匹配“ > ”。
逆序環(huán)視基礎(chǔ)
對于逆序肯定環(huán)視 (?<=Expression) 來說,當(dāng)子表達式 Expression 匹配成功時, (?<=Expression) 匹配成功,并報告 (?<=Expression) 匹配當(dāng)前位置成功。
對于逆序否定環(huán)視 (?<!Expression) 來說,當(dāng)子表達式 Expression 匹配成功時, (?<!Expression) 匹配失??;當(dāng)子表達式 Expression 匹配失敗時, (?<!Expression) 匹配成功,并報告 (?<!Expression) 匹配當(dāng)前位置成功;
順序環(huán)視相當(dāng)于在當(dāng)前位置右側(cè)附加一個條件,所以它的匹配嘗試是從當(dāng)前位置開始的,然后向右嘗試匹配,直到某一位置使得匹配成功或失敗為止。而逆序環(huán)視的特殊處在于,它相當(dāng)于在當(dāng)前位置左側(cè)附加一個條件,所以它不是在當(dāng)前位置開始嘗試匹配的,而是從當(dāng)前位置左側(cè)某一位置開始,匹配到當(dāng)前位置為止,報告匹配成功或失敗。
順序環(huán)視嘗試匹配的起點是確定的,就是當(dāng)前位置,而匹配的終點是不確定的。逆序環(huán)視匹配的起點是不確定的,是當(dāng)前位置左側(cè)某一位置,而匹配的終點是確定的,就是當(dāng)前位置。
所以順序環(huán)視相對是簡單的,而逆序環(huán)視相對是復(fù)雜的。這也就是為什么大多數(shù)語言和工具都提供了對順序環(huán)視的支持,而只有少數(shù)語言提供了對逆序環(huán)視支持的原因。
JavaScript 中只支持順序環(huán)視,不支持逆序環(huán)視。
Java 中雖然順序環(huán)視和逆序環(huán)視都支持,但是逆序環(huán)視只支持長度確定的表達式,逆序環(huán)視中量詞只支持“ ? ”,不支持其它長度不定的量詞。長度確定時,引擎可以向左查找固定長度的位置作為起點開始嘗試匹配,而如果長度不確定時, 就要從當(dāng)前位置向左逐個位置開始嘗試匹配,不成功則回溯,再向左側(cè)位置進行嘗試匹配,然后重復(fù)以上過程,直到匹配成功,或是嘗試到位置 0 處以后,報告匹配失敗, 處理的復(fù)雜度是顯而易見的。
目前只有 .NET 中支持不確定長度的逆序環(huán)視。
逆序環(huán)視匹配過程
?
源字符串: <div> a test </div>
正則表達式: (?<=<div>) [^<]+ (?=</div>)
這個正則的意義就是匹配 <div> 和 </div> 標(biāo)簽之間的內(nèi)容,而不包括 <div> 和 </div> 標(biāo)簽本身。
匹配過程:
首先由“ (?<=<div>) ”取得控制權(quán), 從位置 0 開始匹配,由于位置 0 是起始位置,左側(cè)沒有任何內(nèi)容,所以“ <div> ”必然匹配失敗,從而環(huán)視表達式 “ (?<=<div>) ”匹配失敗,導(dǎo)致整個表達式在位置 0 處匹配失敗。第一輪迭代匹配失敗, 正則引擎向前傳動,由位置 1 處開始嘗試第二次迭代匹配。
直到傳動到位置 5 ,“ (?<=<div>) ”取得控制權(quán),向左查找 5 個位置,由位置 0 開始匹配,由“ <div> ”匹配“ <div> ”成功,從而“ (?<=<div>) ”匹配成功,匹配的結(jié)果為位置 5 ,控制權(quán)交給“ [^<]+ ”;“ [^<]+ ”從位置 5 開始嘗試匹配,匹配“ a test ”成功,控制權(quán)交給“ (?=</div>) ”;由“ </div> ”匹配“ </div> ”成功,從而“ (?=</div>) ”匹配成功,匹配結(jié)果為位置 11 。
此時正則表達式匹配完成,報告匹配成功。匹配結(jié)果為 “ a test ” ,開始位置為 5 ,結(jié)束位置為 11 。其中 “ (?<=<div>) ” 匹配位置 5 , “ [^<]+ ” 匹配 “ a test ” , “ (?=</div>) ” 匹配位置 11 。
逆序否定環(huán)視的匹配過程與上述過程類似,區(qū)別只是當(dāng) Expression 匹配失敗時,逆序否定表達式 (?<!Expression) 才匹配成功。
到此環(huán)視的匹配原理已基本講解完,環(huán)視也就沒有什么秘密可言了,所需要的,也只是多加練習(xí)而已。
3 ?????? 環(huán)視應(yīng)用
今天寫累了,暫時就給出一個環(huán)視的綜合應(yīng)用實例吧,至于環(huán)視的應(yīng)用場景和技巧,后面再整理。
需求:數(shù)字格式化成用“ , ”的貨幣格式。
正則表達式: (?n)(?<=/d)(?<!/./d*)(?=(/d{3})+(/.|$))
測試代碼:
double[] data = new double[] { 0, 12, 123, 1234, 12345, 123456, 1234567, 123456789, 1234567890, 12.345, 123.456, 1234.56, 12345.6789, 123456.789, 1234567.89, 12345678.9 };
foreach (double d in data)
{
??? richTextBox2.Text += " 源字符串 : " + d.ToString().PadRight(15) + " 格式化 : " + Regex.Replace(d.ToString(), @"(?n)(?<=/d)(?<!/./d*)(?=(/d{3})+(/.|$))", ",") + "/n";
}
輸出結(jié)果:
源字符串:0 ????????????? 格式化:0
源字符串:12 ???????????? 格式化:12
源字符串:123 ??????????? 格式化:123
源字符串:1234 ?????????? 格式化:1,234
源字符串:12345 ????????? 格式化:12,345
源字符串:123456 ???????? 格式化:123,456
源字符串:1234567 ??????? 格式化:1,234,567
源字符串:123456789 ????? 格式化:123,456,789
源字符串:1234567890 ???? 格式化:1,234,567,890
源字符串:12.345 ???????? 格式化:12.345
源字符串:123.456 ??????? 格式化:123.456
源字符串:1234.56 ??????? 格式化:1,234.56
源字符串:12345.6789 ???? 格式化:12,345.6789
源字符串:123456.789 ???? 格式化:123,456.789
源字符串:1234567.89 ???? 格式化:1,234,567.89
源字符串:12345678.9 ???? 格式化:12,345,678.9
實現(xiàn)分析:
首先根據(jù)需求可以確定是把一些特定的位置替換為“ , ”,接下來就是分析并找到這些位置的規(guī)律,并抽象出來以正則表達式來表示。
1、 ?? 這個位置的左側(cè)必須為數(shù)字
2、 ?? 這個位置右側(cè)到出現(xiàn)“ . ”或結(jié)尾為止,必須是數(shù)字,且數(shù)字的個數(shù)必須為 3 的倍數(shù)
3、 ?? 這個位置左側(cè)相隔任意個數(shù)字不能出現(xiàn)“ . ”
由以上三條,就可以完全確定這些位置,只要實現(xiàn)以上三條,組合一下正則表達式就可以了。
根據(jù)分析,最終匹配的結(jié)果是一個位置,所以所有子表達式都要求是零寬度。
1、 ?? 是對當(dāng)前所在位置左側(cè)附加的條件,所以要用到逆序環(huán)視,因為要求必須出現(xiàn),所以是肯定的,符合這一條件的子表達式即為“ (?<=/d) ”
2、 ?? 是對當(dāng)前所在位置右側(cè)附加的條件,所以要用到順序環(huán)視,也是要求出現(xiàn),所以是肯定的,是數(shù)字,且個數(shù)為 3 的倍數(shù),即“ (?=(/d{3})+) ”,到出現(xiàn)“ . ”或結(jié)尾為止,即“ (?=(/d{3})+(/.|$)) ”
3、 ?? 是對當(dāng)前所在位置左側(cè)附加的條件,所以要用到逆序環(huán)視,因為要求不能出現(xiàn),所以是否定的,即“ (?<!/./d*) ”
因為零寬度的子表達式是非互斥的,最后匹配的都是同一個位置,所以先后順序是不影響最后的匹配結(jié)果的,可以任意組合,只是習(xí)慣上把逆序環(huán)視寫在左側(cè),順序環(huán)視寫在右側(cè)。
說明:這里只是為了說明環(huán)視的使用而舉的一個例子,實際上這個需求直接用string.Format就可以做到
轉(zhuǎn)自: http://blog.csdn.net/lxcnn/article/details/4304754
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
