Java编译器不会自动优化字符串连接吗?

3
以下Jsoup代码将合并els容器中所有元素的文本:
for (Element el : els)
  entireText += el.text();          

在一个包含大约64个元素的容器中,每个元素包含大约1KB(总共为64KB),这个简单的循环在一般低端Android手机上需要大约8秒
这种缓慢的性能让我有些惊讶,因为我认为Java编译器会用new StringBuilder(A).append(B).append(C).toString()这样的表达式替换A + B + C
难道不是这样吗?
我漏掉了什么?

你有比较过这两个选项,还是只是猜测字符串拼接是你问题的根本原因? - home
@家 当然我做了比较。使用StringBuilder选项可以在不到1秒的时间内完成。 - Souper
1
javac编译器几乎不进行任何优化。即使使用JIT,它也不会对这段代码进行太多优化。开发人员需要知道这是非常低效的。 - Peter Lawrey
2个回答

12

这种缓慢的性能让我感到惊讶,因为我认为Java编译器会用类似于A + B + C替换成new StringBuilder(A).append(B).append(C).toString()的表达式。

因此,编译器会创建以下代码:

for (Element el : els)
  entireText = new StringBuilder(entireText).append(el.text()).toString(); 
你需要在循环之外创建StringBuilder并手动追加。

为什么不在每次迭代中将文本添加到一个StringBuilder实例上?你为什么要在每次迭代中新建StringBuilder呢? - alaster
@mlk 哦,好的。我以为Java编译器比那聪明些。 - Souper
@alaster - 你应该创建一个 StringBuilder。我的代码是为了展示编译器正在做什么,而不是 OP 应该做什么。 - Michael Lloyd Lee mlk
4
@Souper: 它实际上做不到。你请求并构建了一个String变量。编译器无法真正忽略该请求并建立该对象。JVM 理论上可以检测到你除了构建一个新的字符串之外从未使用过该String,并创建一个StringBuilder,但我怀疑任何JVM都没有进行这种优化。 - Joachim Sauer

4
这里的问题在于,第一次迭代创建了一个1k大小的字符串,第二次创建了2k大小的字符串,第三次创建了3k大小的字符串,......而每个字符串都需要复制前面的一个副本。因此,第一次迭代复制了1k文本,第二次复制了2k文本,第三次复制了3k文本,...每次迭代都比上一次慢,并且最后一次迭代分配了一个64k缓冲区并复制了64k数据。
使用StringBuilder(如@mlk所示)意味着您基本上只需要一次性分配64k内存(不完全是这样,但足够接近),并且总共只复制了64k数据(而不是64k+63k+62k+61k+60k+...)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接