String.replaceAll 比自己完成工作要慢得多

Posted

技术标签:

【中文标题】String.replaceAll 比自己完成工作要慢得多【英文标题】:String.replaceAll is considerably slower than doing the job yourself 【发布时间】:2011-09-09 21:10:33 【问题描述】:

我有一段旧代码可以在字符串中查找和替换标记。

它接收fromto 对的映射,遍历它们并针对每一对,遍历目标字符串,使用indexOf() 查找from,并将其替换为值的to。它在StringBuffer 上完成所有工作并最终返回String

我用这一行替换了该代码:replaceAll("[,. ]*", ""); 我还进行了一些比较性能测试。 在比较 1,000,000 迭代时,我得到了:

旧代码:1287ms 新代码:4605ms

3 倍!

然后我尝试将其替换为 3 次调用 replace:replace(",", "");replace(".", "");replace(" ", "");

结果如下:

旧代码:1295 新代码:3524

2 倍!

知道为什么replacereplaceAll 效率这么低吗?我可以做点什么让它更快吗?


编辑:感谢所有答案 - 主要问题确实是 [,. ]* 没有做我想做的事。将其更改为 [,. ]+ 几乎等于非基于正则表达式的解决方案的性能。 使用预编译的正则表达式会有所帮助,但效果不佳。 (这是一个非常适用于我的问题的解决方案。

测试代码:Replace string with Regex: [,. ]*Replace string with Regex: [,. ]+Replace string with Regex: [,. ]+ and Pre-Compiled Pattern

【问题讨论】:

哦,正则表达式的乐趣... 【参考方案1】:

虽然使用正则表达式会带来一些性能影响,但应该没有那么糟糕。

请注意,使用String.replaceAll() 将编译正则表达式每次您调用它。

您可以通过显式使用Pattern 对象来避免这种情况:

Pattern p = Pattern.compile("[,. ]+");

// repeat only the following part:
String output = p.matcher(input).replaceAll("");

另请注意,使用+ 代替* 可以避免替换空字符串,因此也可能加快处理速度。

【讨论】:

@Lukas:它不会改变 what 已完成(在这种情况下,替换是空字符串)。它可能会改变性能特征,但我不知道省略+ 是更快还是更慢。 @Joachim,有趣的是,通过对您的解决方案进行简单的基准测试,[]+ 似乎最快 (1x),[] 在中间 (1.5x),[]* 最慢 ( 2x)。 这个答案假设他收到的地图被一遍又一遍地重复使用。 @Joachim, []* 匹配空字符串“”。因此,对于每个字符,都会找到匹配的 "" 并替换为 ""。这会导致它变慢。您可以通过执行 ....replaceAll("$$$$") 并观察输出来测试它。 @SJuan76:我很清楚这一点(我已经在回答中解释过)。我唯一不知道的是replaceAll("[,. ]+", "") 会比replaceAll("[,. ]", "") 慢还是快。【参考方案2】:

replacereplaceAll 在内部使用 regex,与 StringUtils.replace(..) 相比,在大多数情况下会给出严重的 performance impact。

String.replaceAll():

public String replaceAll(String regex, String replacement) 
        return Pattern.compile(regex).matcher(this ).replaceAll(
             replacement);

String.replace() 在下面使用 Pattern.compile。

public String replace(CharSequence target, CharSequence replacement) 
  return Pattern.compile(target.toString(), Pattern.LITERAL)
         .matcher(this ).replaceAll(
           Matcher.quoteReplacement(replacement.toString()));

另见Replace all occurrences of substring in a string - which is more efficient in Java?

【讨论】:

replace() 不使用正则表达式。 @Adeel,String.replace(char, char) 没有。 String.replace(CharSequence, CharSequence) 确实如此。 @Lukas:这些都没有。顺便说一句,replace(char, char) 毫无疑问,因为它不能使用正则表达式,而且 OP 从未使用过它。 @Lukas String.replace(CharSequence, CharSequence) 使用编译模式,是的,但未针对正则表达式编译。 @Johan 是的,这也是一首儿歌的前三个字。 (这也无关紧要)【参考方案3】:

正如我发表的评论 [,. ]* 匹配空字符串“”。因此,字符之间的每个“空格”都与模式匹配。仅在性能上注明,因为您将很多“”替换为“”。

尝试这样做:

Pattern p = Pattern.compile("[,. ]*");
System.out.println(p.matcher("Hello World").replaceAll("$$$");

返回:

H$$$e$$$l$$$o$$$$$$W$$$o$$$r$$$l$$$d$$$!$$$

难怪“手动”操作会更慢!您应该尝试使用 [,. ]+

【讨论】:

谢谢 - 这解释了为什么它如此努力地执行替换【参考方案4】:

对于replaceAll("[,. ]*", ""),这并不奇怪,因为它依赖于正则表达式。正则表达式引擎创建一个自动机,它在输入上运行。预计会有一些开销。

第二种方法(replace(",", "")...)也在内部使用正则表达式。然而,这里给定的模式是使用Pattern.LITERAL 编译的,因此正则表达式的开销应该可以忽略不计。)在这种情况下,可能是因为Strings 是不可变的(无论你做了多么小的更改,你都会创建一个新字符串),因此不如就地操作字符串的StringBuffers 高效。

【讨论】:

@aiiobe: String.replace(CharSequence, CharSequence) 内部使用正则表达式... 是的,但是Pattern.LITERAL 实际上会关闭所有正则表达式开销。 但这恰恰意味着它没有,你只能将一个正则表达式传递给它,但它不会按预期工作,我想。 不过,LITERAL 模式的编译应该不会太复杂吧? ;-)

以上是关于String.replaceAll 比自己完成工作要慢得多的主要内容,如果未能解决你的问题,请参考以下文章

String replaceAll() 与 Matcher replaceAll()(性能差异)

JavaScript------自定义string.replaceAll()方法

Java String.replaceAll()方法

Java String ReplaceAll 方法给出非法重复错误?

JavaScript String.replaceAll方法实现

用另一个替换特定字符串 - String#replaceAll()