浅谈正则表达式回溯陷阱
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一、匹配场景</a></li><li><a href="#_label1">二、性能测试</a></li><li><a href="#_label2">三、正则的回溯陷阱</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_0">1、了解下NFA与DFA</a></li><li><a href="#_lab2_2_1">2、NFA的回溯</a></li><li><a href="#_lab2_2_2">3、简易例子分析</a></li><li><a href="#_lab2_2_3">4、咋优化?</a></li></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>一、匹配场景</h2><p><strong>判断一个句子是不是正规英文句子</strong></p>
<div class="jb51code"><pre class="brush:plain;">text = "I am a student"</pre></div>
<p>一个正常的英文句子如上,英文单词 + 空格隔开</p>
<p><strong>英文单词</strong> = 多个英文字符 </p>
<p><strong>空格</strong>用 \s 表示</p>
<p>那么一个句子就是<strong>单词</strong> + <strong>空格</strong>(一个或者多个,最后那个单词是0个)(可能有多个单词+空格)+ 最后一个句号 .</p>
<p>那正则就是 </p>
<div class="jb51code"><pre class="brush:plain;">^(+(\s)*)+$</pre></div>
<p><strong> JAVA代码</strong></p>
<div class="jb51code"><pre class="brush:java;">public static void main(String[] args) {
String text = "I am a good student";
String regex = "^(+(\\s)*)+$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
System.out.println(matcher.find());
System.out.println(matcher.group(0));
}</pre></div>
<p><strong>输出结果:</strong> </p>
<blockquote><p>true<br />I am a good student</p></blockquote>
<p class="maodian"><a name="_label1"></a></p><h2>二、性能测试</h2>
<p><a href="https://regex101.com/" rel="external nofollow" target="_blank">regex101: build, test, and debug regex.</a></p>
<p style="text-align:center"><img alt="icon-default.png?t=N7T8" src="https://img.jbzj.com/file_images/article/202311/2023112709283426.png" /></p>
<p>句子改成:<strong>I am a good good student</strong></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283427.png" /></p>
<p>匹配成功了。<strong>39 step,耗时0.1ms,</strong></p>
<p>但是假如把句子拉长点,最后加上一个问号 <strong>?</strong></p>
<p><strong>I am a good good student?</strong></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283428.png" /></p>
<p><strong>83408 step,耗时5.4ms</strong></p>
<p>假如把句子再拉长点,那么直接就干爆CPU,耗时指数增长,</p>
<p><strong>为啥会这样呢?</strong></p>
<p class="maodian"><a name="_label2"></a></p><h2>三、正则的回溯陷阱</h2>
<p class="maodian"><a name="_lab2_2_0"></a></p><h3>1、了解下NFA与DFA</h3>
<ul><li>DFA (Deterministic finite automaton) 确定型有穷自动机</li><li>NFA (Non-deterministic finite automaton) 非确定型有穷自动机</li></ul>
<p>DFA :遍历text字符串,去和Pattern匹配</p>
<p>NFA:遍历Pattern,去与text匹配</p>
<p>DFA(是电动机) 和NFA(汽油机) 都有很长的历史,不过,正如汽油机一样,NFA 的历史更长一些。也有些系统采用了混合引擎,它们会根据任务的不同选择合适的引擎(甚至对同一表达式中的不同部分采用不同的引擎,以求得功能与速度之间的最佳平衡)。 ——《精通正则表达式》</p>
<p><strong>绝大多数编程语言都选择的引擎——NFA (非确定型有穷自动机) 引擎</strong></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283429.png" /></p>
<p class="maodian"><a name="_lab2_2_1"></a></p><h3>2、NFA的回溯</h3>
<p>字符串 :abc</p>
<p>表达式:a(d|b)c</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283430.png" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283431.png" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283432.png" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283433.png" /></p>
<p><strong>注意这个位置回退!!!</strong></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283434.png" /></p>
<p class="maodian"><a name="_lab2_2_2"></a></p><h3>3、简易例子分析</h3>
<div class="jb51code"><pre class="brush:plain;">表达式 = ^(a*)+$
文本 = aaaaaaaaaaaaaaab</pre></div>
<p>走了16w步,花了7.3ms</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283435.png" /></p>
<ul><li>首先 (a*) 已经匹配到 aaaaaaaaaaaaaaa 了,</li><li> (a*)+ 也匹配到 aaaaaaaaaaaaaaa ,</li><li>结束符$去匹配的时候,发现text不是结束,而是一个b</li><li>那吐出最后的a,变成 (aaaaaaaaaaaaaa) a ,没匹配上,继续吐</li></ul>
<div class="jb51code"><pre class="brush:plain;">a* a*
(aaaaaaaaaaaaa) (aa)
a* a* a*
(aaaaaaaaaaaaa) (a)(a)
a* a*
(aaaaaaaaaaaa) (aaa)
a* a* a*
(aaaaaaaaaaaa) (aa)(a)
a* a* a* a*
(aaaaaaaaaaaa) (a)(a)(a)
--> 吐到最后
(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)</pre></div>
<p>直接干爆CPU </p>
<p class="maodian"><a name="_lab2_2_3"></a></p><h3>4、咋优化?</h3>
<p>1、对于 ^(a*)+$</p>
<p>直接把表达式</p>
<p> ^(a*)+$</p>
<p>改成 ^(a*)$</p>
<p><strong>把后面的 + 号去掉。</strong></p>
<p>直接就是 5 Step,0.1ms</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283436.png" /></p>
<p>2、对于 ^(+(\s)*)+$</p>
<p><strong>把后面的 + 号去掉。</strong>就不回溯了,</p>
<p>但是匹配不上,因为语句有问题,就是空格必须存在,但是最后的空格不存在</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283537.png" /></p>
<p>所以改成 :^+(\s+)*$</p>
<p>遇到问号也不回溯</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202311/2023112709283538.png" /></p>
<p>去掉问号也匹配上了</p>
<p style="text-align:center"><img alt="" src="https://img-blog.csdnimg.cn/654ca6bc3d2245d99f3bbd2b2b8f9304.png" /></p>
頁:
[1]