建议46:提高正则表达式执行效率
(1)关注如何让匹配更快失败
正则表达式处理慢往往是因为匹配失败过程慢,而不是匹配成功过程慢。使用正则表达式匹配一个很大字符串的一小部分,情况更为严重,正则表达式匹配失败的位置比匹配成功的位置要多得多。一个修改使正则表达式匹配更快但失败更慢,例如,通过增加所需的回溯次数尝试所有分支的排列组合,这通常是一个失败的修改。
(2)正则表达式以简单的、必需的字元开始
最理想的情况是,一个正则表达式的起始字元应当尽可能快速地测试并排除明显不匹配的位置。用于此目的好的起始字元通常是一个锚(^或$)、特定字符(如x 或u363A)、字符类(如[a-z]或速记符、单词边界(b))。如果可能,避免以分组或选择字元开头,避免顶级分支,如/one|two/,因为这样会强迫正则表达式识别多种起始字元。
Firefox浏览器对起始字元中使用的任何量词都很敏感,能够优化得更好。例如,以ss*替代s+或s{1,}。其他浏览器大多优化掉这些差异。
(3)编写量词模板,使它们后面的字元互相排斥
当字符与字元相邻或子表达式能够重叠匹配时,一个正则表达式尝试分解文本的路径数量将增加。为避免出现此现象,尽量具体化模板。当表达“1”时不要使用“.?”(依赖回溯)。
(4)减少分支的数量,缩小它们的范围
当分支使用 | (竖线)时,可能要求在字符串的每一个位置上测试所有的分支选项。通常可通过使用字符类和选项组件减少对分支的需求,或者将分支在正则表达式上的位置推后(允许到达分支之前的一些匹配尝试失败)。
字符类比分支更快,因为它们使用位向量实现(或其他快速实现)而不是回溯。当分支必不可少时,在不影响正则表达式匹配的情况下,将常用分支放在最前面。分支选项从左向右依次尝试,一个选项被匹配上的机会越多,它被检测的速度就越快。
注意:由于Chrome 和Firefox 浏览器自动执行这些优化中的某些项目,因此较少受到手工调整的影响。
(5)使用非捕获组
捕获组花费时间和内存用于记录后向引用,并保持它们是最新的。如果不需要一个后向引用,可通过使用非捕获组避免这种开销,例如,(?:…)替代(…)。当需要一个完全匹配的后向引用时,有些人喜欢将正则表达式包装在一个捕获组中,这是不必要的,因为可以通过其他方法引用完全匹配。例如,使用regex.exec()返回数组的第一个元素,或替换字符串中的$&。用非捕获组取代捕获组在Firefox 浏览器中影响很小,但在其他浏览器上处理长字符串时影响很大。
(6)捕获感兴趣的文字,减少后处理
如果要引用匹配的一部分,应当通过一切手段,捕获那些片断,再使用后向引用处理。例如,编写代码处理一个正则表达式所匹配的引号中的字符串内容,使用/"(2)"/之后再使用一次后向引用,而不是使用/"2"/之后从结果中手工剥离引号。当在循环中使用时,减少这方面的工作可以节省大量时间。
(7)暴露所需的字元
为帮助正则表达式引擎在如何优化查询例程时做出明智的决策,应尽量简单地判断出那些必需的字元。当字元应用在子表达式或分支中时,正则表达式引擎很难判断它们是不是必需的,有些引擎并不做此方面的努力。例如,正则表达式/^(ab|cd)/暴露它的字符串起始锚。IE 和Chrome浏览器会注意到这一点,并阻止正则表达式尝试查找字符串头端之后的匹配,从而使查找瞬间完成而不管字符串长度。但是,由于等价正则表达式/(^ab|^cd)/不暴露它的^锚,IE无法应用同样的优化,最终无意义地搜索字符串并在每一个位置上匹配。
(8)使用适当的量词
正如建议45所讨论过的那样,“贪婪”量词和“懒惰”量词即使匹配同样的字符串,其查找匹配过程也是不同的。在确保正确等价的前提下,使用更合适的量词类型(基于预期的回溯次数)可以显著提高性能,尤其在处理长字符串时。
(9)将正则表达式赋给变量,以重用它们
将正则表达式赋给变量以避免对它们重新编译。有人使用正则表达式缓存池,以避免对给定的模板和标记组合进行多次编译。不要过分担心,正则表达式编译得很快。重要的是避免在循环体中重复编译正则表达式。换句话说,不要这样做:
while (/regex1/.test(str1)) {
/regex2/.exec(str2);
...
}
替代做法如下:
var regex1 = /regex1/,
regex2 = /regex2/;
while (regex1.test(str1)) {
regex2.exec(str2);
...
}
(10)将复杂的正则表达式拆分为简单的片断
尽量避免一个正则表达式做太多的工作。处理复杂的搜索问题需要将条件逻辑拆分为两个或多个正则表达式,这样更容易解决问题,通常也更高效,每个正则表达式只在最后的匹配结果中执行查找。在一个模板中完成所有工作的正则表达式很难维护,而且容易引起回溯相关的问题。
- "rn
- "