阿里云建站视频教程,php商城源码,商丘网站建设,wordpress 深色主题目录
前言
正则表达式引擎
NFA自动机的回溯
解决方案 前言 正则表达式是一个用正则符号写出的公式#xff0c;程序对这个公式进行语法分析#xff0c;建立一个语法分析树#xff0c;再根据这个分析树结合正则表达式的引擎生成执行程序(这个执行程序我们把它称作状态机程序对这个公式进行语法分析建立一个语法分析树再根据这个分析树结合正则表达式的引擎生成执行程序(这个执行程序我们把它称作状态机也叫状态自动机)用于字符匹配使用不恰当的正则表达式可能会导致很严重的性能问题比如 这个正则表达式看起来没什么问题它可以分为三个部分第一部分匹配 http 和 https 协议第二部分匹配 www. 字符第三部分匹配许多字符其实这里会导致 CPU 使用率高的关键原因就是Java 正则表达式使用的引擎实现是 NFA 自动机这种正则表达式引擎在进行字符匹配时会发生回溯backtracking而一旦发生回溯那其消耗的时间就会变得很长有可能是几分钟也有可能是几个小时时间长短取决于回溯的次数和复杂度 正则表达式引擎 正则表达式引擎就是一套核心算法用于建立状态机简单地说实现正则表达式引擎的有两种方式DFA 自动机Deterministic Final Automata 确定型有穷自动机和 NFA 自动机Non deterministic Finite Automaton 不确定型有穷自动机简单来讲NFA 对应的是正则表达式主导的匹配而 DFA 对应的是文本主导的匹配简单来讲DFA 自动机的时间复杂度是线性的更加稳定但是功能有限而 NFA 的时间复杂度比较不稳定有时候很好有时候不怎么好好不好取决于你写的正则表达式但是胜在 NFA 的功能更加强大所以包括 Java、.NET、Perl、Python、Ruby、PHP 等语言都使用了 NFA 去实现其正则表达式那 NFA 自动机到底是怎么进行匹配的呢我们以下面的字符和表达式来举例说明 要记住一个很重要的点即NFA 是以正则表达式为基准去匹配的也就是说NFA 自动机会读取正则表达式的一个一个字符然后拿去和目标字符串匹配匹配成功就换正则表达式的下一个字符否则继续和目标字符串的下一个字符比较 首先拿到正则表达式的第一个匹配符d于是拿去和字符串的字符进行比较字符串的第一个字符是T不匹配换下一个第二个是o也不匹配再换下一个第三个是d匹配了那么就读取正则表达式的第二个字符a读取到正则表达式的第二个匹配符a拿着继续和字符串的第四个字符 a 比较又匹配了那么接着读取正则表达式的第三个字符y读取到正则表达式的第三个匹配符y拿着继续和字符串的第五个字符 y 比较又匹配了尝试读取正则表达式的下一个字符发现没有了那么匹配结束上面这个匹配过程就是 NFA 自动机的匹配过程但实际上的匹配过程会比这个复杂非常多但其原理是不变的 NFA自动机的回溯 了解了 NFA 是如何进行字符串匹配的接下来我们就可以讲讲这篇文章的重点了回溯为了更好地解释回溯我们同样以下面的例子来讲解 上面的这个例子的目的比较简单匹配以 a 开头以 c 结尾中间有 1-3 个 b 字符的字符串NFA 对其解析的过程是这样子的 首先读取正则表达式第一个匹配符 a 和 字符串第一个字符 a 比较匹配了于是读取正则表达式第二个字符读取正则表达式第二个匹配符 b{1,3} 和字符串的第二个字符 b 比较匹配了但因为 b{1,3} 表示 1-3 个 b 字符串以及 NFA 自动机的贪婪特性也就是说要尽可能多地匹配所以此时并不会再去读取下一个正则表达式的匹配符而是依旧使用 b{1,3} 和字符串的第三个字符 b 比较发现还是匹配于是继续使用 b{1,3} 和字符串的第四个字符 c 比较发现不匹配了此时就会发生回溯发生回溯是怎么操作呢发生回溯后我们已经读取的字符串第四个字符 c 将被吐出去指针回到第三个字符串的位置之后程序读取正则表达式的下一个操作符 c读取当前指针的下一个字符 c 进行对比发现匹配于是读取下一个操作符但这里已经结束了下面我们回过头来看看前面的那个校验 URL 的正则表达式 出现问题的 URL 是 我们把这个正则表达式分为三个部分 第一部分校验协议^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)第二部分校验域名(([A-Za-z0-9-~]).)第三部分校验参数([A-Za-z0-9-~\\/])$我们可以发现正则表达式校验协议 http:// 这部分是没有问题的但是在校验 www.fapiao.com 的时候其使用了 xxxx. 这种方式去校验那么匹配过程是这样的 匹配到 www.匹配到 fapiao.匹配到 com/dzfp-web/pdf/download?request6e7JGm38jf.....你会发现因为贪婪匹配的原因所以程序会一直读后面的字符串进行匹配最后发现没有点号于是就一个个字符回溯回去了这是这个正则表达式存在的第一个问题另外一个问题是在正则表达式的第三部分我们发现出现问题的 URL 是有下划线_和百分号%的但是对应第三部分的正则表达式里面却没有这样就会导致前面匹配了一长串的字符之后发现不匹配最后回溯回去这是这个正则表达式存在的第二个问题 解决方案 明白了回溯是导致问题的原因之后其实就是减少这种回溯你会发现如果我在第三部分加上下划线和百分号之后程序就正常了 运行上面的程序立刻就会打印出match!!但这是不够的如果以后还有其他 URL 包含了乱七八糟的字符呢我们难不成还再修改一遍肯定不现实其实在正则表达式中有这么三种模式贪婪模式、懒惰模式、独占模式在关于数量的匹配中有 ? * {min,max} 四种两次如果只是单独使用那么它们就是贪婪模式如果在他们之后多加一个 ? 符号那么原先的贪婪模式就会变成懒惰模式即尽可能少地匹配但是懒惰模式还是会发生回溯现象例如下面这个例子 正则表达式的第一个操作符 a 与 字符串第一个字符 a 匹配匹配成功于是正则表达式的第二个操作符 b{1,3}? 和 字符串第二个字符 b 匹配匹配成功因为最小匹配原则所以拿正则表达式第三个操作符 c 与字符串第三个字符 b 匹配发现不匹配于是回溯回去拿正则表达式第二个操作符 b{1,3}? 和字符串第三个字符 b 匹配匹配成功于是再拿正则表达式第三个操作符 c 与字符串第四个字符 c 匹配匹配成功于是结束如果在他们之后多加一个 符号那么原先的贪婪模式就会变成独占模式即尽可能多地匹配但是不回溯于是乎如果要彻底解决问题就要在保证功能的同时确保不发生回溯将上面校验 URL 的正则表达式的第二部分后面加多了个 号即变成这样 这样之后运行原有的程序就没有问题了