正则表达式是匹配模式, 要么匹配字符, 要么匹配位置
参考网址
研究匹配原理的时候,必须要知道什么是 ‘回溯’
这个尝试的过程貌似是 一位一位字符或者一组一组字符的尝试(在一组字符中也是一位一位的尝试)
回溯法也称作试探法,基本思想是: 从问题的某一种状态触发(初始状态),搜索这种状态出发所能达到的所有“状态”, 当一条路走到 “尽头” 的时候(不能再前进了),再后退一步或若干步,从另一种可能 “状态” 出发,继续搜索,直到所有 “路径” (状态) 都尝试过。这种不断 “前进”、不断 “回溯”寻找解的方法,称作 “回溯法”。
本质上就是 深度优先搜索算法。 其中退到之前某一步过程我们称之为回溯。从定义来看,当路走不通的时候,就会发生 “回溯”。 即, 尝试匹配失败的时候,接下来一步通常就是回溯。
之前的例子都是贪婪量词相关的,比如 b{1,3}
因为其是贪婪的,尝试可能的顺序是从多往少方向去尝试。首先会尝试 bbb
, 探后再看整个正则是否匹配成功。不能匹配时,吐出一个 ‘b’ , 即在 bb
基础上继续尝试。如果还不行,再吐出一个,再尝试。如果还不行,只能说明匹配失败了。
如果多个贪婪量词紧挨着,并相互冲突,此时会怎样?
答案是: 先下手为强(从左到右依次匹配),因为深度优先搜索。如下:
var string = '12345';
var reg = /(\d{1,3})(\d{1,3})/;
console.log(string.match(reg));
// ["12345", "123", "45", index: 0, input: "12345", groups: undefined]
第一个分组 \d{1,3}
匹配 123
, 第二个分组 \d{1,3}
匹配剩下的 45
惰性量词就是在贪婪量词后面加上问号。表示尽可能少的匹配,比如:
var string = '12345';
var reg = /(\d{1,3}?)(\d{1,3})/;
console.log(string.match(reg));
// ["1234", "1", "234", index: 0, input: "12345", groups: undefined]
第一个分组 \d{1,3}?
匹配 1
, 第二个分组 \d{1,3}
匹配剩下的 234
虽然惰性量词不贪婪,但是也会有 回溯现象。比如正则:
var string = '12345';
var reg = /^(\d{1,3}?)(\d{1,3})$/;
console.log(string.match(reg));
// VM80:3 (3) ["12345", "12", "345", index: 0,input: "12345", groups: undefined]
为了整体的匹配成功,惰性量词子模式也得多塞点。因此最后 \d{1,3}
匹配的字符是 ‘12’, 是两个数字,而不是一个数字。
分支结构在匹配过程中也是惰性的。比如: pattern:/can|candy/
, 去匹配字符串 candy
,的得到结果是 can
,如果前面的满足了,后面的就不会再检验了。
分支结构, 可能前面的子模式会形成局部匹配,如果接下来的表达式整体不匹配时候,仍会继续尝试剩下的分支。这种尝试也可以看成一种回溯。
我的理解还是 在分支结构中,会尽可能匹配,直到匹配成功或者匹配失败
比如:
var reg = /^(?:can|candy)$/;
'candy'.match(reg);
回溯法简单总结为: 正因为有多种可能,所以要一个一个尝试。直到,要么到某一步时候匹配成功,整体匹配成功了;要么都试完后,整体匹配不成功(简而言之,匹配所有可能,直到存在一个成功,或者没有符合匹配模式的(失败))
既然有回溯的过程,匹配效率肯定会低。相对那些 DFA 引擎。
JS 正则引擎是 NFA, NFA 是 “非确定型有限自动机”简写
大部分语言的正则都是 NFA。(NFA 正则引擎具有匹配慢,编译快的特征)