Performance: Java's String.format
Performance: Java's String.format
在Java应用程序中,是否使用String.format而不是StringBuilder是一个好的选择?所以,我写了一个简单的测试,如下所示:
public static void main(String[] args) { int i = 0; Long start = System.currentTimeMillis(); while (i < 10000) { String s = String.format("test %d", i); i++; } System.out.println(System.currentTimeMillis() - start); i = 0; start = System.currentTimeMillis(); while (i < 10000) { String s = new StringBuilder().append("test ").append(i).toString(); i++; } System.out.println(System.currentTimeMillis() - start); }
结果如下:
238 15
因此,如果我的测试有效,StringBuilder比String.format更快。好的。
现在,我开始思考String.format是如何工作的。它是一个简单的字符串拼接,就像`"test " + i`一样吗?
StringBuilder的拼接和String.format有什么区别?有没有一种像String.format一样简单且像StringBuilder一样快的方式?
Performance: Java的String.format
在这个问题中,出现的原因是要比较String.format()和StringBuilder、StringBuffer、普通的String +操作符、String.replace()和String.concat()方法之间的性能差异。下面是一个使用caliper基准测试编写的快速示例代码:
public class StringFormatBenchmark extends SimpleBenchmark { public void timeStringFormat(int reps) { while (--reps >= 0) { String s = String.format("test %d", reps); } } public void timeStringBuilder(int reps) { while (--reps >= 0) { String s = new StringBuilder("test ").append(reps).toString(); } } public void timeStringBuffer(int reps) { while (--reps >= 0) { String s = new StringBuffer("test ").append(reps).toString(); } } public void timeStringPlusOperator(int reps) { while (--reps >= 0) { String s = "test " + reps; } } public void timeReplace(int reps) { while (--reps >= 0) { String s = "test {}".replace("{}", String.valueOf(reps)); } } public void timeStringConcat(int reps) { while (--reps >= 0) { String s = "test ".concat(String.valueOf(reps)); } } public static void main(String[] args) { new Runner().run(StringFormatBenchmark.class.getName()); } }
以下是结果(Java 1.6.0_26-b03,Ubuntu,32位):

显然,String.format()要慢得多(慢上一个数量级)。此外,StringBuffer比StringBuilder要慢得多(正如我们所学到的)。最后,StringBuilder和String +操作符几乎相同,因为它们编译为非常相似的字节码。String.concat()稍微慢一些。
如果只需要简单的连接,也不要使用String.replace()。
如果特殊情况下只有两个连接操作数,则最好将"test ".concat(String.valueOf(reps))添加到基准测试中,因为它更有可能编译成这种形式(对于单个操作没有构建器的好处)。
谢谢,测试非常好。
所以我做了,谢谢你的建议,我更新了我的基准测试和结果。随时在你的计算机上运行测试。
我在我的计算机上运行了它(Java 7,OS X),concat和StringBuilder之间的差距甚至更大。这让我感到惊讶,因为concat表面上做的工作更少(例如,只有一个char[]分配)。在这种情况下,+运算符甚至使用StringBuilder进行编译(当然,这取决于使用的编译器;我使用的是Eclipse的编译器)。
从你所创建的图片中,不要草率得得出结论!你的测试只测量了一个字符串操作的时间!(循环只是为了测试准确性。)如果在循环中构建字符串,StringBuffer和StringBuilder比+运算符要快得多。例如,结果可能是:+运算符在构建500000个字符的字符串时可能需要超过一分钟的时间,而StringBuilder可以在50毫秒以内构建完全相同的字符串!
这个基准测试可以完全优化掉,因为字符串永远不会被使用。将它们的长度相加将有所帮助。
是的,但是你可以像这样使用builder.append(String.format(...))来同时获得可读性和一些速度。在循环中追加字符串确实不是一个选择。
在上述代码中,有两个方法(method1和method2),它们分别使用了不同的方式来拼接字符串。通过运行测试,发现使用StringBuilder拼接字符串的方法比使用String.format方法快了30倍以上。
问题的原因是String.format方法的性能较低。String.format方法是通过格式化字符串来生成新的字符串,这个过程需要进行多次操作,包括字符串的拼接和格式化。而StringBuilder则是一种高效的字符串拼接方法,它使用了可变长度的字符数组来存储字符串,避免了频繁的字符串拼接操作。
解决方法是使用StringBuilder来代替String.format方法进行字符串拼接。这样可以大大提高代码的性能,减少了不必要的字符串操作。
下面是修改后的代码:
public class TestPerf { private static int NUM_RUN; public static void main(String[] args) { NUM_RUN = 100_000; //warm up for (int i = 0; i < 10; i++) { method1(); method2(); } System.gc(); System.out.println("Starting"); long sum = 0; long start = System.nanoTime(); for (int i = 0; i < 10; i++) { sum += method1(); } long end = System.nanoTime(); System.out.println("format: " + (end - start) / 1000000); System.gc(); start = System.nanoTime(); for (int i = 0; i < 10; i++) { sum += method2(); } end = System.nanoTime(); System.out.println("stringbuilder: " + (end - start) / 1000000); System.out.println(sum); } private static int method1() { int sum = 0; for (int i = 0; i < NUM_RUN; i++) { String s = new StringBuilder().append("test ").append(i).toString(); sum += s.length(); } return sum; } private static int method2() { int sum = 0; for (int i = 0; i < NUM_RUN; i++) { String s = String.format("test %d", i); sum += s.length(); } return sum; } }