**##正则表达式匹配过程##
序号 | 过程 | 解释 |
---|---|---|
1 | 编译 | 检查表达式语法正确性,若正确则编译成内部形式 |
2 | 开始传动 | 将引擎放置到字符串开始的位置 |
3 | 监测元素 | 引擎开始测试表达式和文本中的各个元素:1-依次尝试相连元素,直到失败;2-量词,控制权在量词(是否继续匹配)和被限定元素(是否匹配)之间轮换;3-控制权,括号用于捕获,在进入和退出括号时需要开销来修改引擎状态。 |
4 | 寻找匹配结果 | NFA会获取已经匹配的结果而停止引擎,POSIX NFA会一直匹配所有可能,并获取匹配成功的最长的文本 |
5 | 驱动 | 如果找不到匹配,驱动引擎到文本下一个位置,开始新的尝试 |
6 | 彻底失败 | 从开始到最后全部尝试失败则失败 |
一、优化传动
-
1、优化字符串起始点/行锚点
- (1)任何以^开头的表达式必须能够匹配,引擎才会开始工作,所以识别^是必须的
-
(2)^a|^b通常情况下难以确定其实,^(a|b)或者^(?:a|b)可以更快匹配
2、优化字符串结束/行锚点 -
(1)表达式以$或其他结尾锚点来结尾时候,可以从字符串末尾倒数若干字符的位置开始尝试匹配,这样会节省很多时间
3、开头字符/字符组/子串识别优化 -
使用特定的字符或文字子串,通过传动装置更快更准确地定位匹配的开始位置,如
the|them|other
,只能从文本中ot的位置开始匹配。
二、优化表达式本身
-
1、化简量词优化
-
约束普通元素的加号、星号之类的量词,避免引擎大部分逐步处理的开销,比如
.*
和(?:.)*
是逻辑相同,但速度不同的两个表达式,因为.*
会作为一个整体来考虑,省去了括号和量词之间控制权之间的转移所消耗的时间。
2、消除没有必要的括号
3、消除没有必要的字符组,比如[a],单个字符构成的字符组没有直接字符a快
4、忽略优先量词(.*?等)的匹配速度往往比匹配优先量词要慢,尤其当忽略优先量词在捕获型括号内时候。如果字符串很短,差别不明显;如果字符串很长,比如^.*:
和^.*?:
,如果:靠近开头位置,用忽略优先量词比较快,如果在末尾位置用匹配优先量词。
5、使用固化分组和占有优先量词(?<……)来削减备用状态
6、量词等价交换。\d\d\d和\d{3}效果相同,效率后者更高
7、提取多选结构开头的必须元素:用the(?:e|em|re)
代替(?:the|them|there)非常有价值,因为可以更快定位“the”
(?=[JFMASOND])(?:Jan|Feb|…|Dec)更快速定位月份,`JFMASOND就是各个月份首字符
8、模拟开头字符串识别
三、消除循环
-
1、什么是循环
-
所谓循环就是多选结构当中的星号所引起的多次来回匹配。
2、如何解决?
<1>取出多选结构中真正匹配成功的子表达式,按待会提到模式去构建
<2>自顶而下,根据经验和匹配要求通过假设进行构建
3、消除循环通常模式
模式:open nomal*(special nomal*)*closing
举例:
<1>匹配包括转义引号字符串
- 文本:”he said \”hi there\” and left”
- 表达式:"[^\\"]*(\\.[^\\"]*)*"
–**解析:**nomal是[^\\"]+
—————special是\\.
<2>匹配多字符串
- 文本:<b>i <have <a dream</b>
- 表达式:<b>(?>[^<]*)(?>(?!</?b>)<[^<]*)*</b>
–**解析:**nomal是[^<]*
—————special是</?b>
【需要注意的事项】
(1)special和nomal开头不同
(2)nomal必须至少匹配一个字符串
(3)special部分必须是固化的,即文本不能由该部分的多次迭代完成。**