【问题标题】:Optimization of regular expression for custom key-value pairs自定义键值对的正则表达式优化
【发布时间】:2014-10-28 14:05:42
【问题描述】:

我正在尝试从一个大文件中提取一些键值对及其前面的文本,但是使用的正则表达式运行速度很慢,因此需要优化。

输入由相当短的字符串组成,具有 1 或 2 个键值对,例如

one two three/1234==five/5678 some other text

或

one two three/1234==five/5678 some other text four/910==five/1112 more text

使用的(显然次优的)正则表达式是

(.*?)\s*([^ /]+)\s*/\s*([\d]+)\s*==\s*([^ /]+)\s*/\s*([\d]+)\s*

(空格可能出现在字符串中的多个区域,因此会出现重复的\s* 元素。)

测试上述的示例代码:

  public static void main(String[] args) {
    String text = "one two three/1234==five/5678 some other text";
    text = "one two three/1234==five/5678 some other text four/910==five/1112 more text";
    String regex = "(.*?)\\s*([^ /]+)\\s*/\\s*([\\d]+)\\s*==\\s*([^ /]+)\\s*/\\s*([\\d]+)\\s*";
    Matcher matcher = Pattern.compile(regex).matcher(text);
    int end = 0;
    System.out.println("--------------------------------------------------");
    while (matcher.find()) {
      System.out.println("\"" + matcher.group(1) + "\"");
      System.out.println(matcher.group(2) + " == " + matcher.group(3));
      System.out.println(matcher.group(4) + " == " + matcher.group(5));
      end = matcher.end();
      System.out.println("--------------------------------------------------");
    }
    System.out.println(text.substring(end).trim());
  }

输出是键值对,加上前面的文本(所有提取的字段都是必需的)。例如,对于较长的字符串,输出为:

--------------------------------------------------
"one two"
three == 1234
five == 5678
--------------------------------------------------
"some other text"
four == 910
five == 1112
--------------------------------------------------
more text

换句话说,matcher.find() 方法运行 1 轮或 2 轮,具体取决于字符串是短格式还是长格式(分别为 1 或 2 个键值对)。

问题在于提取速度低,有时,根据输入字符串的变化,find() 方法需要很长时间才能完成。

正则表达式有没有更好的形式来显着加快处理速度?

【问题讨论】:

    标签: java regex parsing key-value


    【解决方案1】:

    将(.*?) 放在正则表达式的开头绝不是一个好主意。

    首先,它可能很慢。尽管理论上可以有效地处理非贪婪匹配(例如,参见 Russ Cox 的 re2 实现),但许多正则表达式实现不能很好地处理非贪婪匹配,尤其是在查找操作将失败的情况下。我不知道Java regex 实现是否属于这一类,但没有理由去试探命运。

    第二,没有意义。正则表达式搜索的语义是找到第一个可能的匹配,这与.*?的语义相同。要获取捕获(.*?),您只需要从上一个匹配项的结尾(或字符串的开头)到当前匹配项的开头的子字符串。这很简单,尤其是因为您已经在跟踪上一场比赛的结束。

    【讨论】:

    • 在这种情况下似乎不是速度慢的原因。对于 200-300 个字符长的字符串,为什么 find() 会很慢?无论如何+1。 :-)
    【解决方案2】:

    你是如何阅读文件的?如果您使用BufferedReader#readLine() 或Scanner#nextLine() 逐行读取文件,您只需将\G 添加到正则表达式的开头即可。它就像\A 第一次应用正则表达式时一样,将匹配锚定到字符串的开头。如果该匹配成功,则下一个find() 将锚定到上一个匹配结束的位置。如果它没有找到从开始的匹配项,它会放弃并且不再在该字符串中寻找任何匹配项。

    编辑:我假设您要匹配的每个序列,无论是一个键/值对还是两个,都在自己的行上。如果您一次读取一行文件,则可以在每一行上运行问题中的代码。

    至于为什么你的正则表达式这么慢,这是因为正则表达式引擎必须在它放弃之前在每条不匹配的行上进行多次匹配尝试——可能是数百次。意识到如果在给定线路上的第一次尝试失败,那么在该线路上的进一步尝试将没有任何好处是不够聪明的。所以它会向前移动一个位置并再次尝试。它一直在为整条生产线这样做。

    如果您只希望每行有一个匹配项,我会说使用行首锚点(^ 在 MULTILINE 模式下)。

    【讨论】:

    • 在正则表达式的开头添加 \G 会使 find() 失败。
    • 您是一次处理一行吗?请参阅我的扩展答案。 (抱歉耽搁了;我有段时间不上网。
    • 文件逐行处理,所有行都匹配正则表达式。每行总是有 1 或 2 个匹配项。谢谢回复,随时。 :-)
    猜你喜欢
    • 2012-01-30
    • 1970-01-01
    • 2017-03-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多