在这个集合上执行文本替换的最有效方法是什么?

时间:2011-02-02 18:56:00

标签: java regex performance

想象一下,你有一个List<String>集合,它可以包含数万个字符串。 如果其中一些格式为:

"This is ${0}, he likes ${1},${2} ... ${n}"

将上述字符串转换为以下字符串的最有效方式(性能方面):

"This is %1, he likes %2,%3 ... %n"

请注意,%way从1开始。这是我的解决方案:

import java.util.regex.*;
...
String str = "I am ${0}. He is ${1}";
Pattern pat = Pattern.compile("\\\$\\{(\\d+)\\}");
Matcher mat = pat.matcher(str)
while(mat.find()) {
   str = mat.replaceFirst("%"+(Integer.parseInt(mat.group(1))+1))
   mat = pat.matcher(str);
}
System.out.println(str);

我希望它是有效的Java代码,我现在只是在GroovyConsole中编写它。 我对更有效的解决方案感兴趣,因为我认为在如此多的字符串上应用如此多的正则表达式替换可能太慢了。结束代码将作为Java代码而不是Groovy代码运行,我只使用Groovy进行快速原型设计:)

4 个答案:

答案 0 :(得分:2)

我将如何做到这一点:

import java.util.regex.*;

public class Test
{
  static final Pattern PH_Pattern = Pattern.compile("\\$\\{(\\d++)\\}");

  static String changePlaceholders(String orig)
  {
    Matcher m = PH_Pattern.matcher(orig);
    if (m.find())
    {
      StringBuffer sb = new StringBuffer(orig.length());
      do {
        m.appendReplacement(sb, "");
        sb.append("%").append(Integer.parseInt(m.group(1)) + 1);
      } while (m.find());
      m.appendTail(sb);
      return sb.toString();
    }
    return orig;
  }

  public static void main (String[] args) throws Exception
  {
    String s = "I am ${0}. He is ${1}";
    System.out.printf("before: %s%nafter:  %s%n", s, changePlaceholders(s));
  }
}

<强> test it at ideone.com

appendReplacement()执行两个主要功能:它附加前一个匹配和当前匹配之间的任何文本;它解析组引用的替换字符串,并将捕获的文本插入其位置。我们不需要第二个函数,所以我们通过给它一个空的替换字符串来绕过它。然后我们用生成的替换文本调用StringBuffer的append()方法。

在Java 7中,此API将更加开放,从而进一步优化。 appendReplacement()功能将分解为单独的方法,我们将能够使用StringBuilders而不是StringBuffers(在JDK 1.4中引入Pattern / Matcher时,StringBuilder尚不存在)。

但最有效的优化可能是将模型编译一次并将其保存在static final变量中。

答案 1 :(得分:1)

您应该从每个迭代步骤的字符串的最后一个检查索引开始匹配,而不是第一个索引。正如评论中提到的那样,你的解决方案是O(n ^ 2),它应该是O(n)。为避免不必要的字符串复制,请改为使用StringBuilder:

StringBuilder str = new StringBuilder("I am ${0}. He is ${1}");
Pattern pat = Pattern.compile("\\\$\\{(\\d+)\\}");
Matcher mat = pat.matcher(str);
int lastIdx = 0;
while (mat.find(lastIdx)) {
    String group = mat.group(1);
    str.replace(mat.start(1), mat.end(1), "%"+(Integer.parseInt(group)+1));
    lastIdx = mat.start(1);
}
System.out.println(str);

代码未经测试,因此可能会出现一些错误。

答案 2 :(得分:1)

我认为使用appendReplacement会更有效率,因为那时你没有制作大量新的String对象,并且每次搜索都不会从头开始。

 String str = "I am ${0}. He is ${1}";
 Pattern pat = Pattern.compile("\\$\\{(\\d+)\\}");
 Matcher mat = pat.matcher(str);

 StringBuffer sb = new StringBuffer(str.length());

 while (mat.find()) {
    mat.appendReplacement(sb, "" + Integer.parseInt(mat.group(1)));
 }
 mat.appendTail(sb);

 System.out.println(sb.toString());

打印:

  

我是0.他是1

答案 3 :(得分:1)

试试这个:

String str = "I am ${0}. He is ${1}";
Pattern pat = Pattern.compile("\\$\\{(\\d+)\\}");
Matcher mat = pat.matcher(string);
StringBuffer output = new StringBuilder(string.length());
while(mat.find()) {
   m.appendReplacement(output, "%"+(Integer.parseInt(mat.group(1))+1));
}
mat.appendTail(output);
System.out.println(output);

(主要来自Javadoc,从问题中添加了转换。) 我认为这确实是O(n)。