正则表达式负向后视和前视:等价和性能

Regex negative lookbehind and lookahead: equivalence and performance

我需要一个正则表达式,它将 select 只有那些 URL 不以特定扩展名(如 .png 或 .css.

结尾的字符串

我测试了以下内容:

1) 这个使用负后视:

(?<!\.png|\.css)$

https://regex101.com/r/tW4fO5/1

2) 另一个使用负前瞻:

^(?!.*[.]png|.*[.]css$).*$

https://regex101.com/r/qZ7vA4/1

两者似乎都工作正常,但据说 #1(负向后视)在 436 步中处理(参见 link),而 #2(负向后视)据说在 173 步中处理步骤。

所以我的问题是:这是什么意思?对性能有影响吗?

最后,这两个正则表达式在功能上真的是等价的吗?

编辑:解决方案摘要

总结一下,考虑通过正则表达式排除的字符串结尾的完整列表(典型情况是 Web 服务器设置,其中静态资源由 apache 提供,而动态内容由不同的服务提供)引擎 - 在我的例子中:php-fpm).

PCRE 正则表达式有两个选项:

1) 负面回顾

$(?<!\.(?:ico|gif|jpg|png|css|rss|xml|htm|pdf|zip|txt|ttf)$|(?:js|gz)$|(?:html|woff)$)

https://regex101.com/r/eU9fI6/1

请注意,我使用了多个 OR-ed 后视,因为负后视需要固定宽度的模式(即:您不能混合不同长度的模式)。这使得这个选项编写起来稍微复杂一些。此外,我认为这会降低其性能。

2) 负前瞻

^(?!.*[.](?:js|ico|gif|jpg|png|css|rss|xml|htm|html|pdf|zip|gz|txt|ttf|woff)$).*$

https://regex101.com/r/dP7uD9/1

前瞻比后视稍快。这是100万次迭代的测试结果:

回溯时间 = 18.469825983047 秒
时间预测 = 14.316685199738 秒

如果我没有可变长度模式的问题,我会选择 lookbehind,因为它看起来更紧凑。反正哪一个都好。最后,我选择了前瞻:

<LocationMatch "^(?!.*[.](?:js|ico|gif|jpg|png|css|rss|xml|htm|html|pdf|zip|gz|txt|ttf|woff)$).*$">
    SetHandler "proxy:unix:/var/run/php5-fpm.sock|fcgi://www/srv/www/gioplet/web/public/index.php"
</LocationMatch>

Is it going to have an impact on performances?

在大多数情况下,正则表达式找到匹配项所需的步骤越多,性能就越慢。虽然这也取决于您稍后将使用正则表达式的平台(例如,如果您使用 regex101.com 测试用于 .NET 的正则表达式,这并不意味着它将导致灾难性的回溯,惰性点匹配正则表达式失败带有长文本)。

Are the two regex really functionally equivalent?

不,他们不是。 (?<!\.png|\.css)$ 查找前面没有 .png.css 的行尾。 ^(?!.*[.]png|.*[.]css$).*$ 查找不 包含 .png 或不 .css 结尾的行。要使它们 "equivalent" (也就是说,如果你想确保以 .png.css 结尾的行不匹配),请使用

^(?!.*[.](?:png|css)$).*$
         ^^^^^^^^^^^^

确保 $png css 之后被选中。

正则表达式之间仍然存在差异:第一个只匹配行尾,第二个匹配整行。

有没有办法加快lookbehind解决方案的速度?

请注意,模式 1 中的后视检查在字符串中的每个位置。模式 2 中的前瞻仅在字符串的开头检查 一次 。这就是为什么锚定前瞻解决方案在一种情况下会更快——如果您不能使用仅在少数正则表达式风格(例如 .NET)中可用的 RightToLeft 修饰符。

$(?<!\.(?:png|css)$) 后视解决方案比模式 1 ,因为后视模式在到达 string/line 末尾后仅检查一次。不过,这需要更多的步骤,因为后视的实施比前视的成本更高。

要真正找出哪种解决方案最快,您需要在您的环境中设置性能测试。

第二个或 lookahead 个更快。记住步数不是正确的方法。见 Whosebug question: atomic-groups-clarity.

我已经使用 timeit 在 python 上进行了测试。剧本是

import timeit
s1="""
import re
re.findall(r"^.*(?<!\.png|\.css)$",x,re.M)"""

s2="""
import re
re.findall(r"^(?!.*[.]png$|.*[.]css$).*$",x,re.M)"""

print timeit.timeit(s1,number=1000000,setup='x="""http://gioplet/articles\nhttp://gioplet/img/logo.png\nhttp://gioplet/index.php\nhttp://gioplet/css/main.css"""')

print timeit.timeit(s2,number=1000000,setup='x="""http://gioplet/articles\nhttp://gioplet/img/logo.png\nhttp://gioplet/index.php\nhttp://gioplet/css/main.css"""')

输出:

8.72536265265
7.09159428305