正则的回溯
在正则表达式实现中,回溯是匹配过程的基本组成部分,它是正则表达式如此好用和强大的根源。然而,回溯计算代价很高,如果设计失误,将导致失控。回溯是影响整体性能的唯一因素,理解它的工作原理,以及如何减小使用频率,可能是编写高效正则表达式的关键点。
当一个正则表达式扫描目标字符串时,从左到右逐个扫描正则表达式的组成部分,在每个位置上测试能不能找到一个匹配。对于每一个量词和分支,都必须确定如何继续进行。如果是量词(如*+?或者{2,}),那么正则表达式必须确定何时尝试匹配更多的字符;如果遇到分支(通过|操作符),那么正则表达式必须从这些选项中选择一个进行尝试。
当正则表达式做出这样的决定时,如果有必要,它会记住另一个选项,以备返回后使用。如果所选方案匹配成功,正则表达式将继续扫描正则表达式模板,如果其余部分匹配也成功了,那么匹配就结束了。
但是,如果所选择的方案未能发现相应匹配,或者后来的匹配也失败了,正则表达式将回溯到最后一个决策点,然后在剩余的选项中选择一个。继续这样,直到找到一个匹配,或者量词和分支选项的所有可能的排列组合都尝试失败后放弃这一过程,然后移动到此过程开始位置的下一个字符上,重复此过程。
例如:abc123d
当正则引擎用正则w*(d+)去匹配字符串abc123d时,会先用w*去匹配字符串abc123d,
首先,w*会匹配字符串abc123d的所有字符,
然后再交给d+去匹配剩下的字符串,而剩下的没了,这时,w*规则会吐出一个字符,给d+去匹配,同时,在吐出字符之前,记录一个点,这个点,就是用于回溯的点;
然后d+去匹配n,发现并不能匹配成功,w*再吐出一个字符,w*会先再次记录一个回溯的点,再吐出一个字符。
这时,w* 匹配的结果只有abc12了,已经吐出3d了,d+再去匹配3,发现匹配成功,则会通知引擎,匹配成功了,就直接显示出来了。
所以,(d+)的结果是3,而不是123。
正则表达式中匹配的三种量词:贪婪(Greedy)、勉强(Reluctant)、独占(Possessive)
贪婪 非贪婪(?) 独占(+)
X? X?? X?+
X* X*? X*+
X+ X+? X++
X{n} X{n}? X{n}+
X{n,} X{n,}? X{n,}+
X{n,m} X{n,m}? X{n,m}+
Greedy:贪婪
匹配最长。在贪婪量词模式下,正则表达式会尽可能长地去匹配符合规则的字符串,且会回溯。
Reluctant :非贪婪
匹配最短。在勉强量词模式下,正则表达式会匹配尽可能短的字符串。
Possessive :独占
同贪婪一样匹配最长。不过在独占量词模式下,正则表达式尽可能长地去匹配字符串,一旦匹配不成功就会结束匹配而不会回溯。