字符串连接真的那么慢吗?
Posted
技术标签:
【中文标题】字符串连接真的那么慢吗?【英文标题】:Is string concatenaion really that slow? 【发布时间】:2013-02-17 03:35:42 【问题描述】:我目前正在研究字符串连接选项以及它们对整体性能的影响。我的测试用例产生的结果让我大吃一惊,我不确定我是否忽略了某些东西。
这里是交易:在 java 中执行 "something"+"somethingElse"
将(在编译时)在每次完成时创建一个新的 StringBuilder
。
对于我的测试用例,我正在从我的 HDD 加载一个文件,该文件包含 1661 行示例数据(经典的“Lorem Ipsum”)。这个问题不是关于 I/O 性能,而是关于不同字符串 concat 方法的性能。
public class InefficientStringConcat
public static void main(String[] agrs) throws Exception
// Get a file with example data:
System.out.println("Starting benchmark");
// Read an measure:
for (int i = 0; i < 10; i++)
BufferedReader in = new BufferedReader(
new InputStreamReader(new FileInputStream(new File("data.txt")))
);
long start = System.currentTimeMillis();
// Un-comment method to test:
//inefficientRead(in);
//betterRead(in);
long end = System.currentTimeMillis();
System.out.println("Took "+(end-start)+"ms");
in.close();
public static String betterRead(BufferedReader in) throws IOException
StringBuilder b = new StringBuilder();
String line;
while ((line = in.readLine()) != null)
b.append(line);
return b.toString();
public static String inefficientRead(BufferedReader in) throws IOException
String everything = "", line;
while ((line = in.readLine()) != null)
everything += line;
return everything;
如您所见,两个测试的设置相同。结果如下:
使用inefficientRead()
-方法:
Starting benchmark
#1 Took 658ms
#2 Took 590ms
#3 Took 569ms
#4 Took 567ms
#5 Took 562ms
#6 Took 570ms
#7 Took 563ms
#8 Took 568ms
#9 Took 560ms
#10 Took 568ms
使用betterRead()
-方法
Starting benchmark
#1 Took 42ms
#2 Took 10ms
#3 Took 5ms
#4 Took 7ms
#5 Took 16ms
#6 Took 3ms
#7 Took 4ms
#8 Took 5ms
#9 Took 5ms
#10 Took 13ms
我正在运行java
-command 没有额外参数的测试。我正在运行MacMini3,1 from early 2009 和 Sun JDK 7:
[luke@BlackBox ~]$ java -version
java version "1.7.0_09"
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) Client VM (build 23.5-b02, mixed mode)
这让我觉得差别很大。我在衡量这个时做错了什么,还是应该发生这种情况?
【问题讨论】:
嗯,10 次迭代不足以作为基准测试,但除此之外它似乎 很好。尝试将迭代次数增加到 100,000 次,并添加一个“热身”阶段,在该阶段调用该方法 10,000 次(不计时),让 JIT 进行一些内联。 @CameronSkinner 显然您没有考虑到每个高级迭代已经包含 1661 次方法调用。 @MarkoTopolnik:基准测试正在通过betterRead
或inefficientRead
方法进行测量,因此您应该多次调用这些方法。所以你是对的:你没有考虑被测方法的实现细节。
@CameronSkinner 至少调用这些方法 10,000 次只是为了热身的建议显然是错误的:JVM JIT 根据通过任何单个的次数代码行,这里发生在第十次外部迭代之前。从那开始,您要求执行 100,000 次完整的文件加载过程是非常不合理的。即使是最严格的统计学家,最大 100 的样本量也能满足。
@MarkoTopolnik:我们可以同意不同意。但我建议您详细了解 JIT 在内联整个方法调用等方面可以做什么。
【参考方案1】:
我在衡量这个时做错了什么,还是应该发生这种情况?
这是应该发生的。使用重复的字符串连接构造一个长字符串是一种已知的性能反模式:每个 连接必须创建一个新字符串,其中包含原始字符串的 副本 和一个 copy 附加字符串。你最终会得到 O(N2) 的性能。当您使用StringBuilder
时,大多数时候您只是 将附加字符串复制到缓冲区中。有时缓冲区需要用完空间并需要扩展(通过将现有数据复制到新缓冲区中),但这种情况并不经常发生(由于缓冲区扩展策略)。
请参阅我的article on string concatenation 了解详细信息 - 这是一篇非常古老的文章,所以早于StringBuilder
,但基本面没有改变。 (基本上StringBuilder
类似于StringBuffer
,但没有同步。)
【讨论】:
很好,现在我可以在哪里领取我的“得到 Jon Skeet 的回答”T 恤? 乔恩说的很棒。回溯一步,如果不熟悉,String 是final
和immutable
。【参考方案2】:
这正是应该发生的事情。 betterRead
需要线性时间; inefficientRead
需要二次方时间。
【讨论】:
@MarkoTopolnik:您误解了大 O 复杂度的含义。它衡量的是成本的增长率,而不是绝对成本。如果您将Thread.sleep(1000)
添加到betterRead
方法中,它仍然是O(n),但在挂钟时间上比O(n^2) 方法花费的时间要长得多,至少在n 变大之前是这样。
@MarkoTopolnik:我当然会暗示常数因素无关紧要。
从第一次关心渐近线,到后来优化常数因子。
没有不同的算法最终得到完全相同的常数因子,我很乐意在这里省略它们为何不同的讨论。以上是关于字符串连接真的那么慢吗?的主要内容,如果未能解决你的问题,请参考以下文章