优化自定义键值对的正则表达式

时间:2014-09-03 23:17:53

标签: java regex parsing key-value

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

输入由相当短的字符串组成,包含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()方法需要很长时间才能完成。

是否有更好的正则表达形式,以显着加快处理速度?

2 个答案:

答案 0 :(得分:1)

(.*?)置于正则表达式的开头并不是一个好主意。

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

其次,这是毫无意义的。正则表达式搜索的语义是找到第一个可能的匹配,这与.*?的语义相同。要获取捕获(.*?),您只需要从上一个匹配(或字符串的开头)结束到当前匹配开头的子字符串。这是微不足道的,特别是因为你已经跟踪了上一场比赛的结束。

答案 1 :(得分:1)

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

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

至于为什么你的正则表达式是如此之慢,这是因为正则表达式引擎必须在它之前的每个非匹配行上进行多次匹配尝试 - 可能是数百次。放弃。如果对给定线路的第一次尝试失败,那么在该线路上不再进行任何尝试都不会有任何好处,这是不够智能的。所以它向前冲了一个位置并再次尝试。它一直在为整条生产线做这件事。

如果你只想要每行一个匹配,我会说在MULTILINE模式下使用起始线锚(^)。