1. 引擎的类型
- 传统型NFA
- POSIX NFA
- DFA (不支持忽略优先量词,捕获组和回朔)
Javascript测试代码:
- 首先测试是否是传统型NFA
/** 如果匹配结果是nfa则为传统型NFA **/
const reg = /nfa|nfa not/;
const matches = reg.exec('nfa not');
console.log(matches[0]); // nfa
- 再测试是否是DFA引擎,否则为POSIX NFA
/** 如果匹配时间很短则为DFA,如果匹配时间很长或者程序崩溃则为POSIX NFA **/
const reg = /X(.+)+X/;
const matches = reg.exec('=XXX===============================');
console.log(matches[0]);
2. 一般匹配原则
- 优先选择最左端的匹配结果
/** 匹配结果为belly **/
const reg = /fat|cat|belly|your/;
const matches = reg.exec('The dragging belly indicates that your cat is too fat.');
console.log(matches[0]); // belly
分析:在正则表达式的多选结构所有的选项都可以匹配到下面的字符串,但是belly 是在字符串的最左端
首先得到匹配的。
- 标准的匹配量词(* + ? {m,n}) 是匹配优先的
简而言之,标准匹配量词的结果可能并非所有可能中最长的,但它们总是尝试匹配尽可能多的字符,直到
匹配上限为止。
const reg = /\b\w+s\b/;
const matches = reg.exec('regexes');
console.log(matches[0]);
分析:\w+ 完全能够匹配整个单词,但如果\w+来匹配整个单词,s就无法匹配了,
为了完成匹配,\w+必须匹配regexe,把最后的s留出来。
3. 匹配原理
- NFA引擎(表达式主导)
正则表达式从头开始,每次检查一部分(由引擎查看表达式的一部分),同时检查“当前文本”是否匹配表达式
的当前部分。如果是,则继续表达式的下一部分,如此继续,直到表达式的所有部分都能匹配,即整个表达式
能够匹配成功。简而言之就是以文本来匹配占主导权的正则表达式。
/to(nite|knight|night)/.exec('tonight')
在上面的正则表达式例子中,第一个元素是t,它将会重复尝试,直到在目标字符串中找到't'为止。之后,
就检查紧随其后的字符是否是由o匹配,如果能,就检查下面的元素。下面的元素指(nite|knight|night)
它的真正含义是"nite 或者knight或者night"。引擎会依次尝试这3种可能。我们能够发现,如果匹配的字符串
是tonight,第三个选择能够匹配。但表达式主导的引擎必须完全测试,才能得出结论。
尝试nite的过程与之前一样:“尝试匹配n,然后是i,然后是t,最后是e。”如果这种尝试失败,引擎会选择另
一种可能,如此继续下去,直到匹配成功或是失败。表达式中的控制权在不同元素之间转换,所以称它为”表达
式主导“。
- DFA引擎(文本主导)
DFA引擎在扫描字符串时,会记录“当前有效”的所有匹配可能。我们称之为“文本主导”,是因为它扫描的字符串
中的每个字符都对引擎进行了控制。简而言之就是用正则表达式来匹配主导权的文本。
最左最长规则(DFA 以及POSIX NFA会遵守最左最长规则)
const reg = /one(self)?(selfsufficient)?/;
const matches = reg.exec('oneselfsufficient');
console.log(matches[0]);
传统型NFA:NFA首先会匹配one, 然后是匹配(self)? 留下(selfsufficient)? 来匹配sufficient。它显然
无法匹配,但整个表达式并不会因此匹配失败,因为这个元素不是必须匹配的。所有传统型NFA会返回oneself,
放弃没有尝试的状态。
DFA引擎:DFA会返回更长的结果oneselfsufficient, DFA会同时记录多个匹配,在其中选择最左最长的匹配作为
最终结果。
POSIX NFA: 传统型NFA引擎会在第一次找到匹配时停一下来,但是POSIX NFA则会继续尝试其他分支,穷经
所有的分支,然后从中选择最长的匹配结果。
4. NFA 与 DFA 的比较
-
在预编译阶段
在使用正则表达式搜索之前,两种引擎都会编译表达式,得到一套内化形式,适应各自的匹配算法。NFA的
编译过程通常要快一些,需要的内存也更少一些。 -
匹配速度
正常情况下,两种引擎的匹配速度差不多,一般来说,DFA的速度与正则表达式无关,而NFA则两者直接相关。传统的NFA在报告无法匹配以前,必须尝试正则表达式的所有变体。传统型NFA如果能找到一个匹配,肯定会
停止匹配。POSIX NFA 必须尝试正则表达式的所有变体,确保获得最长的匹配文本,所以如果匹配失败,它所花的时间与传统型NFA一样(有可能更长)。所以POSIX NFA,表达式的效率问题更为重要。
DFA不需要做太多的优化,因为它匹配的速度很快。
-
匹配结果
DFA(或者POSIX NFA)返回最左边最长的匹配文本。传统型NFA可能返回同样的结果,也可能返回别的文本,
因为传统型NFA会在匹配成功时停止匹配,因此不一定得到最长的匹配结果。 -
匹配能力
NFA引擎能提供一些DFA不支持的功能。- 捕获组: 捕获由括号内子表达式匹配的文本。
- 环视: 匹配符合条件的某个位置。
- 忽略优先量词: 非匹配优先,能够匹配尽量少的内容。
- 有序的多选结构: 在多选结构中从左到右依次匹配,不是选择最长的匹配,也不是选择最短的匹配。
- 占有优先量词: 子表达式一旦匹配成功,不会再交还已经匹配的内容。
- 固化分组: 与占有优先量词的功能相同。