正则小结
正则就是一段描述匹配规则的字符串,经解析,能匹配想要的字符串。就好比受害者描述嫌疑人的体貌特征,警察会从众多体貌相近的人中进行筛选,描述得准确,可以减少排查范围,加快破案的速度。传统型NFA就是典型的例子,决定权在于你, 看你的如何描述,如何引导正则引擎去高效地匹配相应内容。大多数人看到正则晦涩难懂,只要求会用, 不去考究它的工作原理, 是很难活学活用的。自己也差不多, 很难系统地去学习正则, 非要写的时候也是参考手册写的, 这次难得有闲情雅致去啃这硬骨头, 比以前了解得稍微多了点, 在此做个小结。
个人是在JS中使用正则的,其中的正则是Perl正则的一个完整的子集,属于传统型NFA。正则引擎分为DFA,传统型NFA ,POSIX NFA。DFA和NFA的最大区别是, DFA是文本主导的,每个字符只会检查一遍,不会回溯,不支持捕获型括号,遵循最左最长原则。POSIX NFA的典型特征是,虽然它是表达式主导的,在匹配到第一个结果以后,会继续尝试所有可能性(分支),直到找到最长的匹配为止。所以相比较而言,POSIX NFA的速度最慢。(/a|ab/去匹配字符串'abc')。
举个较常见的例子,12312312345 ==> 12,312,312,345 (每三位之间加逗号)。分析一下,在添加逗号的地方,右侧是3的整数倍的数字,左侧是1-3个数字。如果对环视(预查)不陌生的话,能很快想到/(?<=\d)(?=(?:\d{3})+)/,将这些位置替换为逗号。但是?<=在js中是不被支持的,得脱离反向环视。s.replace(/\d(?=(?:\d{3})+)/g,function(){return arguments[0]+',';})可以很出色的完成这个任务,当然还有很多的替代方案,例如s.match(/\d(?=(?:\d{3})*)/).join(',') 这里和之前正则唯一的不同就是+改成了*号,我们需要最后3个数字,才能拼接成我们想要的字符串。如果不使用环视呢,我能想到的办法,就是循环。
var r = /(\d)((\d{3})+)$/;
while(r.test(s)){
s = s.replace(r,function(){
var arg = arguments;
return arg[1]+','+arg[2];
});
}
当然这样的代价是很大的,效率会低很多。可见达到目的,方法有很多,如果选择一个最合适的方法,必须得花点本钱花点时间去了解传统型NFA的工作原理。
表达式匹配的过程
1 编译为内部形式。 每个表达式的行程都会消耗一定的资源,对于重复出现的表达式,一定要一次声明,多处使用,尤其是在循环中。
2 开始传动,正则引擎定位到目标字符串的起始位置。如果指定的lastIndex,就是从这个位置开始。
3 依次检测表达式的各个元素,控制权在不同的元素之间切换。
4 寻找合适的结果。传统型一旦检测到合适的结果就会终止检索,如果有g修饰符,会从之前匹配的字符之后去检索,而POSIX NFA会尝试各种可能性,匹配最长的结果。
5 如果失败,会从步骤3重新开始。
6 彻底宣告失败。
优化措施
对其工作原理有一定了解以后,需要通过大量的实践才能归纳出些许优化手段。当然我做的还是很少的,参考书上,照搬到这里。
1 行锚点优化 ^ $, /dasd$/能够从字符串倒数第四位开始匹配。
独立出^$, ^(and) 比 (^abc)效率高,因为第二个在检测锚点之前必须进入到字符串
2 如果正则以 .* 开头,而且没有g, 可认为词表达式开头有一个看不见的^, 减少大量的回溯
3 字符串连接优化,将abc当做一个元素,而不是三个
4 消除不必要的括号 (?:.)* -> .*
5 消除不必要的字符组 [.]->\.
6 (非贪婪)->使用忽略优先量词的时候,引擎通常需要在量词作用对象和该对象之后的字符之间切换,速度较慢。
"(.*?)" *尝试匹配0,查看下一次字符是否是",依次迭代。另外一个缺点就是,"在括号外面,会带来额外的开销。
可以使用[^"]来代替通配符(.), ?也可以去掉。
7 避免指数级(super-linear 超线性)匹配
8 ?>固化分组 和 ?++ 占有优先量词 避免回溯
9 正则一次编译,多次调用,(Perl)减少变量插值
10 使用非捕获型的括号,避免滥用括号, 字符组
11 提取多选结构开头的必须元素(this|that)->th(is|at)
12 忽略优先还是匹配优先,具体情况具体分析。
比如^.*: 相比较于 ^.*?:
前面的正则会匹配到最后一个: 而后面的匹配到第一个:
如果只有一个冒号,如何取舍?
如果:比较靠前 xxx:xxxxxxxxxxxxxxxxxxxxxxxxxx,使用忽略优先
反之,使用匹配优先,因为忽略优先需要检测在量词和:之间切换,表现不如匹配优先; 而此时,匹配优先只需要少量的回溯就可以匹配。
如果数据随机,优先使用匹配优先.
13 多选结构中,出现几率的排在最前面
14 取消循环
传统型NFA是控制能力最强的正则引擎,考量正则的连个标准是准确性和效率,需要花心里在这两则之间找到平衡。所以之前的优化措施显得尤为重要。只有大量的实践以及精益求精的精神才可以提炼出接近完美的正则,虽然有时候正则引擎本身的优化以及强化会极大影响匹配效率,自己能做的是在框架之下做到最好,并且在合适的时机跳出这个框架的约束。