【问题标题】:Regex times out in php 7?正则表达式在 php 7 中超时?
【发布时间】:2017-02-20 20:43:32
【问题描述】:

我下面有 2 个正则表达式,它们在 php 5.6 中有效,但如果字符 + 空格 > 1035 ish,则在 php 7.x 中无效。例如:[spoiler]1035 个 char + ws[/spoiler] 计数。我使用网站http://regexr.com/ 来测试带有http://pastebin.com/5bWzhNvy 的正则表达式,它似乎工作正常。

PHP 7 regex not working the same way as in 5,我可以看到这个问题可能与正则表达式超时有关?但这里是代码,除非我弄错了。

// [spoiler]Text[/spoiler]
preg_match_all('/\[spoiler\]\s*((\s|.)+?)\s*\[\/spoiler\]/', $s, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
    $id = substr(md5($match[1]), 0, 10) . mt_rand(0, 10000);
    $s  = str_replace($match[0],
                      '<a href="javascript:klappe_function(\'' . $id . '\')" style="color: #aaa" title="Open Spoiler"><img src="images/plus.png" id="pic' . $id . '" class="spoiler" alt="spoiler" /></a><div id="k' . $id . '" style="display: none;">' . $match[1] . '</div><br />',
                      $s);
}

// [spoiler=Heading]Text[/spoiler]
preg_match_all('/\[spoiler=(.+?)\]\s*((\s|.)+?)\s*\[\/spoiler\]/', $s, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
    $id = substr(md5($match[2]), 0, 10). mt_rand(0, 10000);
    $s  = str_replace($match[0],
                      '<a href="javascript:klappe_function(\'' . $id . '\')" style="color: #aaa" title="Open ' . myhtmlentities($match[1], ENT_QUOTES)  . '"><img src="images/plus.png" id="pic' . $id . '" class="spoiler" alt="spoiler" /> <b>' . $match[1] . '</b></a><div id="k' . $id . '" style="display: none;">' . $match[2] . '</div><br />',
                      $s);
}

【问题讨论】:

  • 这并不奇怪,正则表达式是一团糟。如果您将(\s|.)+?(“正则表达式性能杀手”)替换为.+? 并使用/s 修饰符,它已经变得更加高效,但仍然没有那么高效。
  • 正则表达式搜索扰流标签内的任何文本,同时去除周围的空白。您可能更适合简单地捕获扰流器标签内的所有文本,然后将空格剥离作为单独的步骤
  • . 已经与\s 匹配,您的正则表达式可能运行缓慢并且超时,因为它进行了大量的回溯。
  • @WillBarnwell: 不完全匹配 . 除了换行符之外的所有字符。
  • 您应该使用preg_replace_callback,而不是使用preg_match_allforeach 循环和str_replace

标签: php regex


【解决方案1】:

正如 cmets 所说,正则表达式效率非常低,这可能是原因 - 你可以用一个正则表达式完成所有这些:

\[spoiler(:?\=([^\]]+))?\]((:?[^\[]+|\[(?!=\/spoiler\]))+)\[\/spoiler\]

  • \[spoiler 匹配 [spoiler
  • (:? 启动一个非捕获组(不会在 $matches 中)
    • \= 匹配 =
    • (启动第一个捕获组
    • [^\]]+ 匹配除 ] 以外的任何字符一次或多次。
    • )关闭第一个捕获组
  • )? 关闭组并使其成为可选
  • (开始第二个匹配组
    • (:? 启动一个非捕获组(不会在 $matches 中)
      • [^\[]+ 匹配除 [ 以外的任何字符一次或多次
      • |
      • \[(?!=\/spoiler\] 单个 [ 字符,只要它后面没有 /spoiler]
    • )+ 重复分组一次或多次
  • )关闭第二个匹配组
  • \[\/spoiler\] 匹配 [/spoiler]

这会处理这两种情况(只需检查 $match[1] 是否为空来判断哪个),但它不会尝试去除空格,您可以在使用 trim($match[1],"\r\n\t ") 后在 php 中轻松做到这一点

这里有两个关键:

匹配“标题”文本

[^\]]+ 匹配除[ 字符之外的任何字符一次或多次。

这比查找任何会导致“回溯”的字符快得多,请参阅:http://www.regular-expressions.info/catastrophic.html

与剧透内容相符

  • (:? 启动一个非捕获组(不会在 $matches 中)
    • [^\[]+ 匹配除 [ 以外的任何字符一次或多次
    • |
    • \[(?!=\/spoiler\] 单个 [ 字符,只要它后面没有 /spoiler]
  • )+ 重复分组一次或多次

这允许在剧透文本中使用[ 字符,但同样不会导致回溯。它通过查找一系列非 [ 字符 单个 [ 字符来做到这一点,只要它后面没有紧跟 /spoiler] - 这是一个“负前瞻”见:http://www.regular-expressions.info/lookaround.html

话虽如此,如果您想高效地完成这项工作,正则表达式可能不是最好的工具,分词器或 preg_split 可能会更好。

【讨论】:

  • 你需要[^\[]+中的+吗?对于该非捕获组外部的+,这似乎是多余的
  • 第一个实例是捕获您的“标题”文本,这比寻找任何字符要好,因为这会导致正则表达式引擎做更多的工作。第二个实例是为了让您仍然可以在剧透中包含 [ 字符,因此它会查找除 [ 字符以外的任何内容,或者特别是未在结尾的其余部分之后的 [ 字符标记文本。
猜你喜欢
  • 2010-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-26
  • 1970-01-01
  • 2012-03-16
  • 1970-01-01
  • 2023-03-23
相关资源
最近更新 更多