正则小结

  正则就是一段描述匹配规则的字符串,经解析,能匹配想要的字符串。就好比受害者描述嫌疑人的体貌特征,警察会从众多体貌相近的人中进行筛选,描述得准确,可以减少排查范围,加快破案的速度。传统型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是控制能力最强的正则引擎,考量正则的连个标准是准确性和效率,需要花心里在这两则之间找到平衡。所以之前的优化措施显得尤为重要。只有大量的实践以及精益求精的精神才可以提炼出接近完美的正则,虽然有时候正则引擎本身的优化以及强化会极大影响匹配效率,自己能做的是在框架之下做到最好,并且在合适的时机跳出这个框架的约束。

 

转载于:https://www.cnblogs.com/cy056/p/3166198.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值