给你的正则加加速

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34009967/article/details/80740762

高性能的正则

啦啦啦 我是一个搬运工,搬错打轻点儿 _-


说说性能

因为DFA是文本主导的,所以没什么意思,我们主要来看看NFA。有时候一个细微的改变 就可能给表达式带来翻天覆地的变化。写正则的时候,需要在准确率和效率之间做权衡。

一些例子

“(\. | [^\])*”

这个例子 匹配字符串 允许出现转义的 “,转义的字符由前者匹配,非转义字符 前者匹配失败-回溯-匹配后者成功
我们先来看正常情况(就是转义字符占少数,正常情况不会一句话都是转移字符吧????)
我们下面来匹配1\”df45\” sdfdsfdsfdsf这个字符串

表达式 回溯次数
“(\. | [^\”])*” .1\”.d.f.4.5\”. .s.d.f.d.s.f.d.s.f.d.s.f.
“([^\”] | \.)*” 1.\”df45.\” sdfdsfdsfdsf.
  • 这种改动对POSIX NFA 没有影响,因为他本来就需要匹配所有情况,但是对NFA好处很大
  • 匹配成功才有效果,匹配失败 替换顺序对NFA也没用(如果最后一次成功 貌似也没啥用HHH)
  • 有人说了,”([^”] | (?<=\))*” 环视也可以解决,比如匹配 dfd\”fdfd\”

    • ”/sfdsdf\” fdsf ” sdfsdf ” ==> “/sfdsdf\” fdsf “,虽然\” 这里表达的是转义的\ + “,但是环视会把\”匹配到,然后 fdsf 之后的 ” 当做结束的引号
  • “( \. | [^”] )*”这个行不行呢?里面无非就是转义字符 \. + 非转义字符 [^”]

    • 如果是 “sfdsdfdsf sdfdsf \”fdfdf \” fddsfdsfdsf, “( \. | [^”] )*会匹配所有字符,然后释放字符给结尾的 ” 匹配,最后的转义 ” 会被认为是结尾的 “
    • 这种还有救没了呢? 必须有 使用占有优先量词 或者是 固话分组 “( \. | [^”] )+” “(?> \. | [^”] )“,会抛弃回溯 不会释放字符给结尾的 ” 匹配
    • 这个可交换分支位置 “( [^”] | \. )*”? 不可以!!! “sfdsdfdsf sdfdsf \”fdfdf \” fddsfdsfdsf ” 匹配到的是 “sfdsdfdsf sdfdsf \”

上面的表达式,不管是交换分支之前 还是之后 都有一个问题,就是 * 会把表达式控制权在括号内外不听的切换,所以 是不是要有一个种优化方式:减少进出次数? 怎么办呢—尽量在括号没匹配更多的字符 [^\”]+

表达式 回溯次数
“(\. | [^\”])*” .1\”.d.f.4.5\”. .s.d.f.d.s.f.d.s.f.d.s.f.
“(\. | [^\”]+)*” .1\”.df45\”. sdfdsfdsfdsf.
“([^\”] | \.)*” 1.\”df45.\” sdfdsfdsfdsf.
“([^\”]+ | \.)*” 1.\”df45.\” sdfdsfdsfdsf.

可见 在上面这种里面,是大大减少了回溯次数的;下面的表达式中 也大大减少了进出口号的次数
总体来说减少了维护子表达式 匹配位置 回溯版本的开销性能会提升不少

填坑

生活就是真么苦逼,搞发明总是有代价了。上面的最后一种发明,对NFA是天使,对POSIX NFA来说 就是噩梦了
([^\”])*我们只看这段,括号内的是 * 号的约束对象,用来匹配有某些字符,追都就是匹配到所有字符。(NFA POSIX NFA都是这样的)。但是如果是 ([^\”]+)* ,就不一样了。+ 代表一次或者多次, * 代表任意多次,所有情况的总和是多少?不知道。。。不过它有一个名字:超线性匹配,就是指数级别的增长
这种增长对POSIX NFA,就是回溯了。因为它要匹配所有情况,然后去最长的作为结果

  • 一次假的死机 (见邮箱项目)
  • 如果任何时候都可以很快出结果 DFA
  • 如果有时候是很快出结果(成功比较靠前 就快) 是NFA
  • 如果可以成功 但是一直很慢 POSIX NFA
  • 有一些优化 可以让POSIX NFA 出现类似 DFA的效果(250!!!!!!!!!)

说说回溯

我们用 ” . * ” 做实验,匹配 dfdfd fdfd ” dfdsfsdf ” fdfd fdfdf ” dfsfsdfsdf ” fdssdfsdff
结果就是: dfdfd fdfd 1”9 dfdsfsdf “78 fdfd fdfdf “56 dfsfsdfsdf “34 fdssdfsdff2

扫描二维码关注公众号,回复: 3694113 查看本文章
  • 1到2–释放字符-到3 此时NFA结束
  • POSIX NFA会继续类似 234一样 进行 456 678 89,然后先NFA的结果作为最终结果(当然 89不会成功,因为第一个 ” 不会释放),期间依靠的就是驱动装置 在匹配成功之后 驱动进入下一轮回溯

如果匹配失败呢

” . * ” & 这种,是不会匹配成功的,但是正则还是会进行运作,进行四轮尝试 分别在 四个 ” 号那里

优化

换成 ” [ ^ ” ] * “

  • 一直失败 一直到第一个引号 匹配成功 ” dfdsfsdf “,POSIX 在这里会说一次 但是只是在这段字符之间回溯一次,然后进入下一轮匹配
  • 一直失败 匹配成功第二个” dfsfsdfsdf “,不会匹配第 23引号之前的文本,因为第一轮消耗了第二个引号(环视不消耗字符)
    • ’123 “1111” 22222 “3333” 4444’.match(/”[^”]*”/) 这个可以试试

避免多选结构

a | b | c 比 [ abc ] 好垃圾的多,因为 如果匹配abcbcaabc 每次前进一步 就会进行3次匹配

想点骚东西

正则表达式的执行,分为编译、传动开始、元素检查、出匹配结果 ,23循环进行,在每一步都可以进行一些优化
比如使用开头结尾优化啊(^开头没有匹配到 就是失败了)、消除不必要的括号(.* 替换 (?:.))、忽略优先量词优化、占有优先量词避免回溯、量词互换(\d\d\d 换成 \d{4} 后者某些时候快一点)、拆分正则为小表达式 分别匹配
在面向对象的处理中,避免重编译啊

通用的表达式匹配算法

。。。待续

猜你喜欢

转载自blog.csdn.net/qq_34009967/article/details/80740762