正则表达式负向后视和前视:等价和性能
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
我需要一个正则表达式,它将 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