目录
- 匹配原理
- 优化原则
匹配原理
正则表达式之所以能处理复杂文本,是因为采用了有穷状态自动机。那什么是有穷状态自动机呢?有穷状态是指一个系统具有有穷个状态,不同的状态代表不同的意义。自动机是指系统可以根据相应的条件,在不同的状态下进行转移。从一个初始状态,根据对应的操作执行状态转移,最终达到终止状态(可能有一到多个终止状态)。
有穷状态自动机主要包括确定性有穷自动机,简称 DFA (Deterministic finite automaton) 和 非确定性有穷自动机,简称 NFA (Non-deterministic finite automaton) 两种。在正则表达式中有穷状态自动机的具体实现称为”正则引擎”。
各个编程语言使用正则表达式时,经常会”compile”一下正则表达式来提升效率,这个编译过程其实就是生成自动机的过程。正则引擎会拿着这个自动机和字符串进行匹配。比如正则表达式 a(?:bb)+a 生成的自动机可能是这样的:
在状态 s3 时,不需要输入任何字符,状态也有可能转换成 s1 。你可以理解成 a(bb)+a 在匹配了字符 abb 之后,到底在 s3 状态,还是在 s1 状态,这是不确定的。这种状态机就是非确定性有穷状态自动机(Non-deterministic finite automaton 简称 NFA)。
- NFA 工作机制
NFA 是以表达式为主导的,先看正则表达式,再看文本。NFA 以表达式为主导,它的引擎是使用贪心匹配回溯算法实现。NFA 通过构造特定扩展,支持子组和反向引用。但由于 NFA 引擎会发生回溯,即它会对字符串中的同一部分,进行很多次对比。因此,在最坏情况下,它的执行速度可能非常慢。
- DFA 工作机制
DFA 则是以文本为主导,先看文本,再看正则表达式。一般来说,DFA 引擎会更快一些,因为整个匹配过程中,字符串只看一遍,不会发生回溯,相同的字符不会被测试两次。DFA 引擎可以确保匹配到可能的最长字符串。但由于 DFA 引擎只包含有限的状态,所以它没有反向引用功能;并且因为它不构造显示扩展,它也不支持捕获子组。
优化原则
- 测试性能的方法
可以通过工具测试正则表达式的性能,比如 regex101.com。
- 尽量准确地表示匹配范围
比如我们要匹配引号里面的内容,除了写成 ”.+?” 之外,我们可以写成 ”[^”]+” 。使用 [^”] 要比使用点号好很多,虽然使用的是贪婪模式,但它不会出现点号将引号匹配上,再吐出的问题。
- 提取出公共部分
通过对 NFA 引擎的学习,相信你应该明白 (abcd|abxy) 这样的表达式,可以优化成 ab(cd|xy) ,因为 NFA 以正则为主导,会导致字符串中的某些部分重复匹配多次,影响效率。
- 出现可能性大的放左边
由于正则是从左到右看的,把出现概率大的放左边,域名中 .com 的使用是比 .net 多的,所以我们可以写成 \.(?:com|net)\b ,而不是 \.(?:net|com)\b 。
- 只在必要时才使用子组
在正则中,括号可以用于归组,但如果某部分后续不会再用到,就不需要保存成子组。通常的做法是,在写好正则后,把不需要保存子组的括号中加上 ?: 来表示只用于归组。如果保存成子组,正则引擎必须做一些额外工作来保存匹配到的内容,因为后面可能会用到,这会降低正则的匹配性能。
- 警惕嵌套的子组重复
如果一个组里面包含重复,接着这个组整体也可以重复,比如 (.*)* 这个正则,匹配的次数会呈指数级增长,所以尽量不要写这样的正则。
- 避免不同分支重复匹配
在多选分支选择中,要避免不同分支出现相同范围的情况。