神话
用加号运算符连接两个字符串是万恶之源
-匿名Java开发人员
注意 :此处讨论的测试的源代码可以在Github上找到
从大学时代起,我就学会了使用+运算符将Java中的String
连接视为致命的性能缺陷。 最近,在Backbase R&D上进行了一次内部审查,其中每当您使用plus运算符连接String时,由于在javac
使用StringBuilder
javac
,这种重复的口头禅就被当作神话了。 我准备证明这一点并验证不同环境下的现实。
考试
依靠编译器优化String
串联意味着根据您采用的JDK供应商,情况可能会发生重大变化。 就我日常工作所需的平台支持而言,应考虑三个主要供应商:
- Oracle JDK
- IBM JDK
- ECJ-仅适用于开发人员
此外,尽管我们正式支持Java 5至6,但我们也正在考虑为我们的产品提供Java 7支持,在这三个供应商的基础上增加了另外三层的间接。 为了 懒惰 为简单起见, ecj
编译的字节码将使用单个JDK(即Oracle JDK7)运行。 我准备了安装了上述所有JDK的Virtualbox VM,然后开发了一些类来表示三种不同的串联方法,根据特定的测试用例,每种方法调用总计三至四个串联。 每个测试回合都运行测试类数千次,每个测试用例总共进行100回合。 相同的VM用于运行同一测试用例的所有回合,并跨不同的测试用例重新启动,所有这些都使Java运行时能够执行所有可能的优化,而不会以任何方式影响其他测试用例。 缺省选项用于启动所有JVM。 可以在基准运行程序脚本中找到更多详细信息。
编码
Github上提供了测试用例和测试套件的完整代码。 产生了以下不同的测试用例,以测量String串联的性能差异,加上直接使用StringBuilder
:
// String concat with plus
String result = 'const1' + base;
result = result + 'const2';
// String concat with a StringBuilder
new StringBuilder().append('const1').append(base).append('const2').append(append).toString();
}
//String concat with an initialized StringBuilder
new StringBuilder('const1').append(base).append('const2').append(append).toString();
一般的想法是在变量的常量String
的开头和结尾都提供一个串联。 后两种情况(两者都显式使用StringBuilder
之间的区别在于后者使用1-arg构造函数,该构造函数使用结果的初始部分初始化构建器。
结果
足够多的讨论,您可以在下面查看生成的图形,其中每个数据点对应一个测试回合(例如,同一测试类的1000次执行)。 随后将讨论结果以及更多细节。
讨论
甲骨文JKD5显然是输家,与其他甲骨文相比似乎处于B联赛。 但这实际上不是本练习的范围,因此我们暂时将其忽略。 也就是说,我在上图中观察到了另外两个有趣的地方。 首先是,使用加号运算符与使用显式StringBuilder
通常确实存在很大的差异, 尤其是如果您使用的Oracle Java5的执行树时间比其他工作人员差的话。
第二个观察结果是,虽然对于大多数JDK来说,显式StringBuilder
通常提供的速度是常规plus运算符的两倍,但IBM JDK6似乎不会遭受任何性能损失,它总是平均需要25ms才能完成任务。所有测试用例。 仔细查看生成的字节码会发现一些有趣的细节
字节码
注意:反编译类也可以在Github上使用。在所有可能的JDK中,即使存在加号, StringBuilders
始终用于实现String
串联。 而且,在所有厂商和版本中,同一测试用例几乎没有任何区别 。 唯一的区别是ecj
,这是唯一巧妙地优化CatPlus
测试用例以调用StringBuilder
的1-arg构造函数而不是0-arg版本的方法。
比较生成的字节码可以揭示在不同场景下可能影响性能的因素:
- 与plus串联时,只要发生串联,就会创建
StringBuilder
新实例 。 由于构造函数的无用调用,这很容易导致性能下降,并且由于丢弃实例而给垃圾收集器带来更多压力 - 如果且仅当您在原始代码中以这种方式编写
StringBuilder
,编译器才会照搬您,并且仅使用String的1-arg构造函数来初始化StringBuilder
。 这分别导致对CatSB和CatSB2的StringBuilder.append
四个和三个调用。
结论
字节码分析为原始问题提供了最终答案。 您是否需要显式使用StringBuilder
来提高性能? 是上面的图清楚地表明,除非使用IBM JDK6运行时,否则在使用plus运算符时,您将损失50%的性能,尽管在显式StringBuilder
时,它在候选对象上的表现会稍差一些。 同样,看到JIT优化如何影响整体性能也很有趣:例如,即使在两个显式StringBuilder
测试用例之间存在不同的字节码,从长远来看,最终结果也绝对相同。
参考: Java StringBuilder神话从我们的JCG合作伙伴 Carlo Sciolla在Skuro博客中揭穿 。
翻译自: https://www.javacodegeeks.com/2013/03/java-stringbuilder-myth-debunked.html