1、为什么要知道些正则匹配原理
知其然,知其所以然。
2、正则匹配引擎
正则引擎大体上可分为不同的两类:DFA和NFA。DFA(Deterministic finite automaton) 确定型有穷自动机,NFA (Non-deterministic finite automaton) 非确定型有穷自动机。
2.1 NFA
|
|
|
|
2.2 DFA
|
|
例:
字符串:this is yansen’s dog
正则表达式:/ya(msen|nsen|nsem)/
NFA工作方式:先在字符串中查找y,然后匹配其后是否为a; 如果是a则继续查找其后是否为m;如果不是则匹配其后是否为n(此时淘汰msen支分支); 然后继续看其后是否依次为s,e;接着测试是否为n,是n则匹配成功,不是则测试是否为m。为什么是 m ?因为 NFA 工作方式是以正则表达式为标准,反复测试字符串,这样同样一个字符串有可能被反复测试了很多次!
DFA工作方式:从this中t开始依次查找y,定位到y,已知其后为a,则查看表达式是否有a,此处正好有a;然后字符串a后为n,DFA依次测试表达式,此时msen不符合要求淘汰。nsen和nsem符合要求,然后DFA依次检查字符串,检测到sen中的n时只有nsen分支符合,则匹配成功!
3、基础知识
3.1 字符串组成
对于字符串“reg”而言,包括三个字符和四个位置,位置0在r前面,位置4在g后面。
3.2 占有字符和零宽度
正则表达式匹配过程中,如果子表达式匹配到的是字符内容,而非位置,并被保存到最终的匹配结果中,那么就认为这个子表达式是占有字符的;如果子表达式匹配的仅仅是位置,或者匹配的内容并不保存到最终的匹配结果中,那么就认为这个子表达式是零宽度的。
占有字符是互斥的,零宽度是非互斥的。也就是一个字符,同一时间只能由一个子表达式匹配,而一个位置,却可以同时由多个零宽度的子表达式匹配。
3.3 控制权和传动
正则的匹配过程,通常情况下都是由一个子表达式(可能为一个普通字符、元字符或元字符序列组成)取得控制权,从字符串的某一位置开始尝试匹配,一个子表达式开始尝试匹配的位置,是从前一子表达匹配成功的结束位置开始的。如正则表达式:
(子表达式一)(子表达式二)
假设**(子表达式一)为零宽度表达式,由于它匹配开始和结束的位置是同一个,如位置0,那么(子表达式二)**是从位置0开始尝试匹配的。
假设**(子表达式一)为占有字符的表达式,由于它匹配开始和结束的位置不是同一个,如匹配成功开始于位置0,结束于位置2,那么(子表达式二)**是从位置2开始尝试匹配的。
而对于整个表达式来说,通常是由字符串位置0开始尝试匹配的。如果在位置0开始的尝试,匹配到字符串某一位置时整个表达式匹配失败,那么引擎会使正则向前传动,整个表达式从位置1开始重新尝试匹配,依此类推,直到报告匹配成功或尝试到最后一个位置后报告匹配失败。
4、正则表达式简单匹配过程
4.1 基础匹配过程
原字符串:reg
正则表达式:/reg/
匹配过程:由字符r取得控制权,从位置0开始匹配,由r来匹配r,匹配成功,控制权交给字符e,因为之前字符r从位置0已经匹配成功,所以字符e从位置1开始匹配,e匹配e,匹配成功,接着到字符g匹配g,也匹配成功。
正则表达式匹配完成,匹配结果为reg,开始位置0,结束位置3。
4.2 含有量词的匹配过程(1)
原字符串:reg
正则表达式:/re?g/
量词?属于优先匹配量词,在可匹配可不匹配时,会优先选择尝试匹配,当这种选择使得整个表达式无法匹配成功时,才会让出匹配的内容。
匹配过程:由字符r取得控制权,从位置0开始匹配,r匹配r,匹配成功,控制权交给e?,e?尝试优先匹配e,匹配成功,控制权交给g,且保留一个备选状态,最后由g匹配g,匹配成功,丢弃备选状态,匹配结束。
4.3 含有量词的匹配过程(2)
原字符串:rg
正则表达式:/re?g/
匹配过程:由字符r取得控制权,从位置0开始匹配,r匹配r,匹配成功,控制权交给e?,e?匹配g,匹配失败,保留一个备选状态,由于匹配失败,进行回溯,找到备选状态,此时控制权到g,字符g匹配g,匹配成功。
4.4 含有量词的匹配过程(3)
原字符串:reg
正则表达式:/re?x/
匹配过程:由字符r取得控制权,从位置0开始匹配,r匹配r,匹配成功,控制权交给e?,e?尝试优先匹配e,匹配成功,控制权交给x,且保留一个备选状态,x匹配g,匹配失败,此时进行回溯,找到记录的备选状态,此时控制权交给x,x匹配e,匹配失败,第一轮匹配失败。
正则引擎使正则向前传动,由位置1开始尝试匹配,由r匹配e,匹配失败,没有备选状态,第二轮匹配尝试失败。正则引擎继续向前传动,知道位置3匹配失败,匹配结束,此时整个正则表达式匹配失败。
4.5 零宽度匹配过程
源字符串:a12
正则表达式:^(?=[a-z])[a-z0-9]+$
元字符^和$匹配的只是位置,顺序环视(?=[a-z])只进行匹配,并不占有字符,也不将匹配的内容保存到最终的匹配结果,所以都是零宽度的。
匹配过程:从元字符^开始,匹配位置0,匹配成功,控制权交给顺序环视(?=[a-z]);
(?=[a-z])要求它所在位置右侧必须是字母才能匹配成功,零宽度的子表达式之间是不互斥的,即同一个位置可以同时由多个零宽度子表达式匹配,所以它也是从位置0尝试进行匹配,位置0的右侧是字符“a”,符合要求,匹配成功,控制权交给[a-z0-9]+;
因为(?=[a-z])只进行匹配,并不将匹配到的内容保存到最后结果,并且(?=[a-z])匹配成功的位置是位置0,所以[a-z0-9]+也是从位置0开始尝试匹配的,[a-z0-9]+首先尝试匹配a,匹配成功,继续尝试匹配,可以成功匹配接下来的1和2,此时已经匹配到位置3,位置3的右侧已没有字符,这时会把控制权交给$;
$匹配结束位置,也就是位置3,匹配成功。
参考资料:
4、正则表达式的原理