正则表达式的引擎分析
正则表达式的引擎有两个特点:
1.默认情况下都是贪婪匹配。
2.引擎总是着急的,会返回最先匹配到结果正则引擎的工作原理举例:
当把cat应用到“He captured a catfish for his cat”,引擎先比较c和“H”,结果失败了。于是引擎再比较c和“e”,也失败了。直到第四个字符,c匹配了“c”。a匹配了第五个字符。到第六个字符t没能匹配“p”,也失败了。引擎再继续从第五个字符重新检查匹配性。直到第十五个字符开始,cat匹配上了“catfish”中的“cat”,正则表达式引擎急切的返回第一个匹配的结果,而不会再继续查找是否有其他更好的匹配。
回溯机制
回溯原理的简单解释
前面的引擎分析,只是针对简单的正则匹配分析,但是我们都知道正则表达式中还有很多模糊的条件匹配。这时候引擎该如何处理呢?对于计算机而言,处理模糊匹配的最好办法就是穷举,把所有可能的结果都找一遍,别忘了引擎本身都是着急的,也就是说当他匹配到一种可能性之后就不会再匹配剩余的结果。其中这种根据可能性查找的机制就叫回溯。
举个例子:用正则表达式 <.+>匹配字符串“This is a <EM>first</EM> test”,在这里首先<.+会先匹配出 “<EM>first</EM> test”,但是当>匹配换行符的时候会失败,如果前面你没有用.+这类的模糊语义的话,字符串就会匹配失败,但是由于你用.+这种模糊语义,等于给予了计算机更多的可能性,于是计算机会从最近的可能找起,因此>就会按照可能的顺序去匹配“t”,
“s”,“e”···一直匹配到">",由于引擎是着急的,所以他马上就返回了匹配结果“<EM>first</EM>”。分支和回溯
下面的例子演示了这一过程是如何处理分支的。
/h(ello|appy) hippo/.test("hellothere, happyhippo");
此正则表达式匹配“hellohippo”或“happyhippo”。测试一开始,它要查找一个h,目标字符串的第一个字母恰好就是h,它立刻就被找到了。接下来,子表达式(ello|appy)提供了两个处理选项。正则表达式选择最左边的选项(分支选择总是从左到右进行),检查ello是否匹配字符串的下一个字符。确实匹配,然后正则表达式又匹配了后面的空格。然而在这一点上它走进了死胡同,因为hippo 中的h 不能匹配字符串中的下一个字母t。此时正则表达式还不能放弃,因为它还没有尝试过所有的选择,随后它回溯到最后一个检查点(在它匹配了首字母h 之后的那个位置上)并尝试匹配第二个分支选项。但是没有成功,而且也没有更多的选项了,所以正则表达式认为从字符串的第一个字符开始匹配是不能成功的,因此它从第二个字符开始,重新进行查找。它没有找到h,所以就继续向后找,直到第14 个字母才找到,它匹配happy的那个h。然后它再次进入分支过程。这次ello未能匹配,但是回溯之后第二次分支过程中,它匹配了整个字符串“happyhippo”。匹配成功了。重复与回溯
下一个例子显示了带重复量词的回溯。
//测试用的字符串
var str = "<p>Para </p>" +
"<img src='smiley.jpg'>" +
"<p>Para </p>" +"<div>Div.</div>";
//正则表达式匹配测试
/<p>.*<\/p>/i.test(str);
正则表达式一上来就匹配了字符串开始的<p>
。然后是.*
。点号匹配除换行符以外的任意字符,星号这个贪婪量词表示重复零次或多次——匹配尽量多的次数。因为目标字符串中没有换行符,它将吞噬剩下的全部字符串!不过正则表达式模板中还有更多内容需要匹配,所以正则表达式尝试匹配<
。它在字符串末尾匹配不成功,所以它每次回溯一个字符,继续尝试匹配<
,直到它回到</div>
标签的<
位置。然后它尝试匹配\/(转义反斜杠)
,匹配成功,然后是p
,匹配不成功。正则表达式继续回溯,重复此过程,直到第二段末尾时它终于匹配了</p>
。匹配返回成功,它从第一段头部一直扫描到最后一个的末尾,这可能不是你想要的结果。
-
注意事项
1.注意减少正则表达式的回溯次数,如果回溯过多,会导致回溯失控出现,即 当一个正则表达式占用浏览器上秒,上分钟或者更长时间。
2.尽可能的使用简单,明确的语义去编写,正则表达式。
3.其他优化事项见正则表达式优化诀窍
- 参考文章
1.深入浅出正则表达式一
2.深入浅出正则表达式二
3.正则表达式中的回溯