性能

模式中可能出现的某些项目比其他项目效率更高。使用字符类(如 [aeiou])比使用一组备选方案(如 (a|e|i|o|u))效率更高。一般来说,提供所需行为的最简单结构通常是最有效的。Jeffrey Friedl 的书中有很多关于优化正则表达式以提高性能的讨论。

当模式以 .* 开头并且设置了 PCRE_DOTALL 选项时,模式会隐式地由 PCRE 锚定,因为它只能匹配主题字符串的开头。但是,如果未设置 PCRE_DOTALL,PCRE 无法进行此优化,因为 . 元字符不会匹配换行符,如果主题字符串包含换行符,则模式可能会从主题字符串中其中一个换行符之后的字符开始匹配,而不是从开头开始。例如,模式 (.*) second 匹配主题 "first\nand second"(其中 \n 代表换行符),第一个捕获的子字符串为 "and"。为了做到这一点,PCRE 必须从主题字符串中的每个换行符之后重新尝试匹配。

如果您在不包含换行符的主题字符串中使用此类模式,则通过设置 PCRE_DOTALL 或以 ^.* 开头模式来指示显式锚定可以获得最佳性能。这可以节省 PCRE 沿主题扫描以查找换行符以重新开始的步骤。

注意包含嵌套的不确定重复的模式。当应用于不匹配的字符串时,这些模式可能需要很长时间才能运行。考虑模式片段 (a+)*

这可以在 33 种不同的方式中匹配 "aaaa",并且随着字符串变长,这个数字会非常快地增长。(* 重复可以匹配 0、1、2、3 或 4 次,对于除 0 之外的每个情况,+ 重复可以匹配不同的次数。)当模式的其余部分使得整个匹配将失败时,PCRE 原则上必须尝试每种可能的变体,这可能需要非常长的时间。

一项优化可以捕获一些更简单的案例,例如 (a+)*b,其中一个文字字符位于后面。在开始标准匹配过程之前,PCRE 检查主题字符串中是否稍后出现 "b",如果不存在,则立即失败匹配。但是,当没有后续文字时,此优化无法使用。您可以通过比较 (a+)*\d 和上面模式的行为来观察差异。前者在应用于整行 "a" 字符时几乎立即失败,而后者对于超过 20 个字符的字符串需要相当长的时间。

添加注释

用户贡献的注释 1 个注释

arthur200126 at gmail dot com
6 个月前
> 注意包含嵌套的不确定重复的模式。当应用于不匹配的字符串时,这些模式可能需要很长时间才能运行。

说它需要 "很长时间" 是一种轻描淡写:所需的时间将呈指数级增长,具体来说是 2^n,其中 n 是 "a" 字符的数量。这种行为会导致 "正则表达式拒绝服务" (ReDoS),如果您对用户提供的输入运行此类表达式。

为了不被 ReDoS 击中,请执行以下三项操作中的某一项(或可能不止一项)

* 编写不会造成漏洞的表达式。 https://regexper.cn/redos.html 是一份很好的资源("原子" 和 "占有" 选项在 PHP/PCRE 中都可用)。如果您无法用眼睛发现所有问题,请使用 "ReDoS 检测器" 或 "正则表达式 linter"。
* 为 preg_match 设置一些限制。在 https://php.net/manual/en/pcre.configuration.php. 上提到的值上使用 `ini_set(...)`。降低限制可能会导致正则表达式失败,但这通常比使整个服务器停顿更好。
* 使用不同的正则表达式实现。曾经有一个 RE2 扩展;现在没有了!
To Top