正則表達式 貪婪與非貪婪的回溯

先推一個不錯的js正則表達式在線資源:http://www.jb51.net/tools/zhengze.html

概念不提,看例子:

貪婪:

var re=/<script>.+<\/script>/g;
jsstr = "<script<script>alert(document.cookie)<\/script>>alert(document.cookie)<\/script>";

var result=jsstr.match (re);
alert (result[0]);//<script>alert(document.cookie)<\/script>>alert(document.cookie)<\/script>

非貪婪:

var re=/<script>.+?<\/script>/g;
jsstr = "<script<script>alert(document.cookie)<\/script>>alert(document.cookie)<\/script>";

var result=jsstr.match (re);
alert (result[0]);//<script>alert(document.cookie)<\/script>

分析:

貪婪就是即使發現了匹配標籤<\/script>(中間的),表達式依然貪想着後面可能還有這樣的匹配標籤,沒有還則罷了,有就是最後發現的。(還有點(.)匹配除換行符 \n 之外的任何單個字符,所以中間出現的<\/script>就被當做任意字符處理了);

非貪婪就是隻要遇到匹配標籤<\/script>就停止了,纔不管後面得東東,只要滿足就行了。

 

貪婪、非貪婪引起的回溯

回溯1:

var re=/\w*(\d+)/g;

var str="cfc456n";

var result=str.match (re);
alert (result[0]);//6

疑惑:爲什麼結果不是456呢?

分析:當正則引擎用正則\w*(\d+)去匹配字符串cfc456n時,會先用\w*去匹配字符串cfc456n,首先,\w*會匹配字符串cfc456n的所有字符,然後再交給\d+去匹配剩下的字符串,而剩下的沒了,這時,\w*規則會不情願的吐出一個字符,給\d+去匹配,同時,在吐出字符之前,記錄一個點,這個點,就是用於回溯的點,然後\d+去匹配n,發現並不能匹配成功,會再次要求\w*再吐出一個字符,\w*會先再次記錄一個回溯的點,再吐出一個字符。這時,\w* 匹配的結果只有cfc45了,已經吐出6n了,\d+再去匹配6,發現匹配成功,則會通知引擎,匹配成功了,就直接顯示出來了。所以,(\d+)的結果是6,而不是456。

回溯2:

var re=/\w*?(\d+)/g;

var str="cfc456n";

var result=str.match (re);
alert (result[0]);//456

疑惑:這個又是什麼個原理呢?

分析:正則表達式有條規則,是量詞優先匹配,所以\w*?會先去匹配字符串cfc456,由於\w*?是非貪婪,正則引擎會用表達式\w+?每次僅匹配一個字符串,然後再將控制權交給後面的\d+去匹配下一個字符,同時,記錄一個點,用於在匹配不成功的時候,返回這裏,再次匹配,也就是回溯點。由於\w後面是量詞是*,*表示0到無數次,所以,首先是0次,也就是\w*?匹配個空,記錄回溯點,將控制權交給\d+,\d+去匹配cfc456n的第一個字符c,然後,匹配失敗,於是乎,接着講控制權交給\w*?去匹配cfc456n的c,\w*?匹配c成功,由於是非貪婪,所以,他每次只匹配一個字符,記錄回溯點,然後再將控制權交給\d+匹配f,接着,\d+匹配f再失敗,再把控制權給\w*?,\w*?再匹配c,記錄回溯點(這時\w*?匹配結果是cfc了),再把控制權給\d+,\d+去匹配4,匹配成功,然後,由於量詞是+,就是1到無數次,所以,接着往後匹配,再匹配5,成功,再接着,再匹配6,成功,再接着,繼續匹配操作,下一個字符是n,匹配失敗,這時,\d+會吧控制權交出去。由於\d+後面已經沒有正則表達式了,所以,整個正則表達式宣告匹配完成,其結果就是 cfc456, 其中第一組結果是456。


額外的例子

var re=/<script>.+?<\/script>/g;
jsstr = "<script>***。。。<\/script>";//長度大於100014

var result=jsstr.match (re);
alert (result[0]);//null

結果不能匹配成功?其原因就是回溯太多了,直到造成耗盡棧空間爆棧。

 

練習:用下面的re1,re2,re3去匹配str,分別有幾次回溯?

var str = "<script>123456<\/script>";
var re1=/<script>.+<\/script>/g;

var re2=/<script>.+?<\/script>/g;
var re3=/<script>(?:(?!<\/script>).)+<\/script>/g; //7次

解析:re1:9次。(<script>)匹配"<script>",(.+)匹配"123456<\/script>",此時(<\/script>)發現沒得匹配了,就要求回溯,每回溯一次(.+)就會讓出一個字符,這樣9次之後就可以滿足條件了。

re2:5次。(<script>)匹配"<script>",(.+)匹配"1",此時輪到(<\/script>)匹配了,可是(<\/script>)發現不能匹配,於是要求回溯使得(.+)再次匹配一個"2",此後又輪到(<\/script>),還不匹配,再回溯,如此5次,最終在(.+)匹配了"123456"後,(<\/script>)也得以匹配成功

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章