【问题标题】:Regex pattern match performance in Java for long stringJava中长字符串的正则表达式模式匹配性能
【发布时间】:2016-05-03 15:00:52
【问题描述】:

我有一个正则表达式,在找到匹配项时效果很好(500 纳秒),但在没有匹配项时需要很长时间(超过 3 秒)。我怀疑这可能是因为回溯。我尝试了一些选项,例如根据一些文档将.* 转换为(.*)?,但没有帮助。

输入:一个很长的字符串 - 在某些情况下是 5k 个字符。

要匹配的正则表达式:.*substring1.*substring2.*

我正在预编译模式并重新使用匹配器,我还能尝试什么?

这是我的代码 sn-p - 我将使用数百万个不同的输入字符串调用此方法,但只有少数正则表达式模式。

private static HashMap<String, Pattern> patternMap = new HashMap<String, Pattern>();
private static HashMap<String, Matcher> matcherMap = new HashMap<String, Matcher>();

这是我的方法:

public static Boolean regex_match(String line, String regex) {
    if (regex == null || line == null) {
      return null;
    }
    if (!patternMap.containsKey(regex)) {
      patternMap.put(regex, Pattern.compile(regex));
      matcherMap.put(regex,patternMap.get(regex).matcher(""));
    }
    return matcherMap.get(regex).reset(line).find(0);
 }

【问题讨论】:

  • 你的目标是什么?你需要使用正则表达式吗?
  • 请出示您的代码
  • @Pshemo - 是的,我必须使用正则表达式。
  • 是否有需要.*前后的理由?如果您使用 find() 而不是 match() 并去掉表达式上的 .* 前缀和后缀,它应该会快得多。
  • 这些模式是硬编码的还是动态构建的?我可以建议使用像 substring1[^s]*(?:s(?!ubstring2)[^s]*)*substring2 这样的展开模式

标签: java regex


【解决方案1】:

正如您所暗示的,您的正则表达式会遇到称为灾难性回溯的问题。本质上,第一个.* 将匹配整个字符串,然后回溯直到substring1 匹配。这将与substring2 重复。因为substring2 失败,第二个.* 需要寻找另一个substring2 开始匹配的地方,然后它会再次失败。每次substring1 匹配时,我们都需要检查substring2 可能匹配的每个位置。

您已经在使用pattern.find(),因此您可以省略开始和结束.*。然后,将内部的 .* 更改为 .*? 可以通过将贪婪匹配器变为惰性匹配器来提高性能。

这会产生:substring1.*?substring2

【讨论】:

  • 完美。这比我拥有的正则表达式性能要好得多。感谢您的回答。
【解决方案2】:

如果你使用indexOf(),你可以验证模式是否匹配:

int pos1 = str.indexOf("substring1");
int pos2 = str.indexOf("substring2", pos1);

if(pos1 != -1 && pos2 != -1){
  // regex
}

当正则表达式不匹配时,您将获得灾难性的回溯。实际上,即使存在匹配项,您的模式也可能会进行大量回溯。 .* 会吃掉整个字符串,然后需要往回走,不情愿地还给字符。

如果你的字符串看起来像:substring1 substring2........50000 more characters......,那么你会用懒惰的.*? 获得更好的性能。请注意(.*)?.*? 不同。

正则表达式的性能会因子字符串是什么以及它们所匹配的内容而异。如果您的字符串看起来像:substring1........50000 more characters...... substring2,那么您将使用现有的.* 获得更好的性能。

【讨论】:

    【解决方案3】:

    如果情况足够简单,您可以使用 String.indexOf() 比使用 Regex 快得多。您可以将您的问题重新编码为:

    public static boolean containsStrings(String source, String string1, String string2) {
      long pos1, pos2;
      pos1 = source.indexOf(string1);
      if(pos1 > -1) {
        pos2 = source.indexOf(string2,pos1 + string1.length);
        if(pos2 > pos1 && source.indexOf(string1,pos2 + string2.length) < -1) {
          return true;
        }
      }
      return false;
    }
    

    请注意,我的解决方案不处理 string2 包含在 string1 中的情况,如果是这种情况,您需要将其添加到逻辑中。

    【讨论】:

    • 这个想法很好,但是如果string2 出现两次,一次出现在string1 之后,这将失败。最好先找到string1,然后将其索引作为搜索string2的起始索引
    • 不幸的是,这不起作用,因为我的函数应该能够处理任何正则表达式。不过感谢您的回答。
    • @user100001 太糟糕了,在一个有 20mb 文本的实际案例中,我尽可能多地使用 indexOf()contains(),而正则表达式仅用于复杂案例。在大型文档上谨慎使用正则表达式的性能提升了几个数量级。
    【解决方案4】:

    ^((?!substring1).)*substring1((?!substring2).)*substring2.*?\Z

    应该这样做,因为包含一个子字符串多次但不是同时包含两个子字符串的字符串不会回溯令人作呕。如果您不需要匹配器在输入结束时结束,则可以将 .*?\Z 放在末尾。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多