正则表达式回溯陷阱

一、匹配场景

判断一个句子是不是正规英文句子

bash 复制代码
text = "I am a student"

一个正常的英文句子如上,英文单词 + 空格隔开

英文单词 = 多个英文字符 [a-zA-Z]

空格用 \s 表示

那么一个句子就是单词 + 空格(一个或者多个,最后那个单词是0个)(可能有多个单词+空格)+ 最后一个句号 .

那正则就是

bash 复制代码
^([a-zA-Z]+(\s)*)+$

JAVA代码

java 复制代码
public static void main(String[] args) {
        String text = "I am a good student";
        String regex = "^([a-zA-Z]+(\\s)*)+$";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        System.out.println(matcher.find());
        System.out.println(matcher.group(0));
    }

输出结果:

bash 复制代码
true
I am a good student

二、性能测试

regex101: build, test, and debug regexRegular expression tester with syntax highlighting, explanation, cheat sheet for PHP/PCRE, Python, GO, JavaScript, Java, C#/.NET, Rust.https://regex101.com/

句子改成:I am a good good student

匹配成功了。39 step,耗时0.1ms,

但是假如把句子拉长点,最后加上一个问号

I am a good good student?

83408 step,耗时5.4ms

假如把句子再拉长点,那么直接就干爆CPU,耗时指数增长,

为啥会这样呢?

三、正则的回溯陷阱

1、了解下NFA与DFA

  • DFA (Deterministic finite automaton) 确定型有穷自动机
  • NFA (Non-deterministic finite automaton) 非确定型有穷自动机

DFA :遍历text字符串,去和Pattern匹配

NFA:遍历Pattern,去与text匹配

DFA(是电动机) 和NFA(汽油机) 都有很长的历史,不过,正如汽油机一样,NFA 的历史更长一些。也有些系统采用了混合引擎,它们会根据任务的不同选择合适的引擎(甚至对同一表达式中的不同部分采用不同的引擎,以求得功能与速度之间的最佳平衡)。 ------《精通正则表达式》

绝大多数编程语言都选择的引擎------NFA (非确定型有穷自动机) 引擎

2、NFA的回溯

字符串 :abc

表达式:a(d|b)c

注意这个位置回退!!!

3、简易例子分析

bash 复制代码
表达式 = ^(a*)+$
文本 = aaaaaaaaaaaaaaab

走了16w步,花了7.3ms

  1. 首先 (a*) 已经匹配到 aaaaaaaaaaaaaaa 了,
  2. (a*)+ 也匹配到 aaaaaaaaaaaaaaa ,
  3. 结束符$去匹配的时候,发现text不是结束,而是一个b
  4. 那吐出最后的a,变成 (aaaaaaaaaaaaaa) a ,没匹配上,继续吐
复制代码
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)

直接干爆CPU

4、咋优化?

1、对于 ^(a*)+$

直接把表达式

^(a*)+$

改成 ^(a*)$

把后面的 + 号去掉。

直接就是 5 Step,0.1ms

2、对于 ^([a-zA-Z]+(\s)*)+$

**把后面的 + 号去掉。**就不回溯了,

但是匹配不上,因为语句有问题,就是空格必须存在,但是最后的空格不存在

所以改成 :^[a-zA-Z]+(\s[a-zA-Z]+)*$

遇到问号也不回溯

去掉问号也匹配上了

相关推荐
钢铁男儿4 小时前
Python 正则表达式(正则表达式和Python 语言)
python·mysql·正则表达式
钢铁男儿4 小时前
Python 正则表达式实战:解析系统登录与进程信息
开发语言·python·正则表达式
拾心2111 小时前
【运维进阶】Linux 正则表达式
linux·运维·正则表达式
莲动渔舟11 小时前
第4.3节:awk正则表达式详解-特殊字符
正则表达式·编程语言·awk
G_H_S_3_13 小时前
【网络运维】Linux:正则表达式
linux·运维·网络·正则表达式
yuxb7315 小时前
Linux 文本处理与 Shell 编程笔记:正则表达式、sed、awk 与变量脚本
linux·笔记·正则表达式
烟锁池塘柳01 天前
【R语言】R 语言中 gsub 与正则表达式详解(含 POSIX 与 Perl 风格实例)
正则表达式·r语言·perl
郝学胜-神的一滴4 天前
基于C++的词法分析器:使用正则表达式的实现
开发语言·c++·程序人生·正则表达式·stl
ruleslol5 天前
python30-正则表达式
python·正则表达式
Big Cabbage7 天前
python 正则表达式
python·正则表达式