正则表达式负面观察和前瞻:等同性和表现

时间:2016-02-18 08:42:28

标签: regex regex-lookarounds negative-lookahead negative-lookbehind

我需要一个正则表达式,它只选择那些不以.png或.css等特定扩展名结尾的URL字符串。

我测试了以下内容:

1)这个使用负面的后视:

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

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

2)另一个使用否定前瞻:

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

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

两者似乎都运行良好,但据说#1(负面后视)按436步处理(参见链接),而#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 lookbehinds,因为负向lookbehind需要一个固定宽度的模式(即:你不能混合不同长度的模式)。这使得这些选项的编写变得更加复杂。此外,我认为这会降低其表现。

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>

2 个答案:

答案 0 :(得分:3)

  

会对演出产生影响吗?

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

  

这两个正则表达式在功能上是否真的相同?

不,他们不是。 (?<!\.png|\.css)$会找到不在.png.css之前的行的结尾。 ^(?!.*[.]png|.*[.]css$).*$找到不包含 .png的行或 .css结尾的行。使它们等同于#34; (也就是说,如果您想确保以.png.css结尾的行不匹配),请使用

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

确保在否定前瞻中$ png之后检查css

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

有没有办法加速后视解决方案?

请注意,在字符串内的每个位置处检查模式1中的lookbehind 。模式2中的前瞻只在字符串的最开头检查一次。这就是为什么锚定前瞻解决方案在一个条件下会更快 - 如果你不能使用只有少数正则表达式(例如.NET)的RightToLeft修饰符。

$(?<!\.(?:png|css)$)后瞻性解决方案比模式1更快,因为在到达字符串/行的末尾后,只检查一次后看模式。尽管如此,由于实施了比前瞻更昂贵的后视镜,这需要更多的步骤。

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

答案 1 :(得分:2)

第二个或lookahead更快。记住步数不是正确的方法。请参阅Stackoverflow 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