提高表达式速度的技巧
传统型 NFA 引擎允许我们主导引擎更快地匹配,但有时提高了表达式的匹配效率,却让其难以维护和理解,所以更多的时候我们需要权衡,修改表达式带来好处的程度来决定应用以下这些技巧:
使用起始锚点
若以.*
开头的正则表达式都应在最前面添加^
或\A
,因为,如果该表达式在某字符串开头不能匹配,那么它一定不能在该字符串的其它位置匹配。
使用非捕获型括号
若不需要引用括号内的文本,则使用非捕获型括号(?:)
。这样不但能节省捕获时间,而且还能减少回溯使用的状态数量。
从量词中“提取”必须的元素
比如,用xx*
替代x+
能“提取”必须匹配的x
。
“提取”多选结构开头的必须元素
比如,用regexp(?:er|js)
替代(?:regexper|regexpjs)
,提取必须匹配的元素regexp
。
在表达式前面或末尾独立出锚点
^(?:ako|luv)
和^ako|^luv
在逻辑上等价,但传统型NFA
引擎只会对前者进行优化,所以前者的效率更高。
独立出结尾锚点也是一样的原理。
比如:(?:ako|luv)$
与ako$|luv$
。
拆分正则表达式
有时候,应用多个小正则表达式的速度比一个大正则表达式快的多。
比如,依次检查ako
、luv
之类的速度要比检查ako|luv
的速度快,尤其是检查元素增多后。原因也很简单,因为后者不存在必须匹配成功的文字内容,故不会进行“内嵌文字字符串检查优化”。
模拟开头字符识别
通过在表达式开头添加环视结构进行手动的开头字符识别优化,在正则表达式的其他部分匹配之前,环视结构可以进行“预查”,选择合适的开始位置。
比如可以将上例中的Ako|Luv
改写成肯定正序环视结构(?=[AL])(?:Ako|Luv)
,这样写的好处是匹配结果更精确,但性能可能会降。
使用固化分组与占有优先量词
在多数情况下,固化分组和占有优先量词能极大地提高匹配速度,而且不会改变匹配结果。
比如^[^:]+:
中的冒号第一次尝试是无法匹配,那么任何回溯都是没有意义的。而使用固化分组^(?>[^:]+):
或者占有优先量词^[^:]++:
能直接抛弃备用状态或者根本不创建多少备用状态。由于引擎没有内容状态可以进行回溯,就不会进行不必要的回溯而增加开销。
将最可能匹配的多选分支放在前面
若匹配正确与顺序无关时,那么应该将最有可能匹配的多选分支放在首位。
比如主机名匹配中的(?:com|org|net|cn|me)\b
,将应用最广泛的排在最前面。