给定下面的2个toString()实现,哪个是首选的:
public String toString(){
return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}
or
public String toString(){
StringBuilder sb = new StringBuilder(100);
return sb.append("{a:").append(a)
.append(", b:").append(b)
.append(", c:").append(c)
.append("}")
.toString();
}
?
更重要的是,鉴于我们只有3个属性,它可能不会有什么不同,但在什么时候你会从+ concat切换到StringBuilder?
这取决于字符串的大小。
请看下面的例子:
static final int MAX_ITERATIONS = 50000;
static final int CALC_AVG_EVERY = 10000;
public static void main(String[] args) {
printBytecodeVersion();
printJavaVersion();
case1();//str.concat
case2();//+=
case3();//StringBuilder
}
static void case1() {
System.out.println("[str1.concat(str2)]");
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
String str = "";
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str = str.concat(UUID.randomUUID() + "---");
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}
static void case2() {
System.out.println("[str1+=str2]");
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
String str = "";
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str += UUID.randomUUID() + "---";
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}
static void case3() {
System.out.println("[str1.append(str2)]");
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
StringBuilder str = new StringBuilder("");
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str.append(UUID.randomUUID() + "---");
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}
static void saveTime(List<Long> executionTimes, long startTime) {
executionTimes.add(System.currentTimeMillis() - startTime);
if (executionTimes.size() % CALC_AVG_EVERY == 0) {
out.println("average time for " + executionTimes.size() + " concatenations: "
+ NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(() -> 0))
+ " ms avg");
executionTimes.clear();
}
}
输出:
java bytecode version:8
java.version: 1.8.0_144
[str1.concat(str2)]
average time for 10000 concatenations: 0.096 ms avg
average time for 10000 concatenations: 0.185 ms avg
average time for 10000 concatenations: 0.327 ms avg
average time for 10000 concatenations: 0.501 ms avg
average time for 10000 concatenations: 0.656 ms avg
Created string of length:1950000 in 17745 ms
[str1+=str2]
average time for 10000 concatenations: 0.21 ms avg
average time for 10000 concatenations: 0.652 ms avg
average time for 10000 concatenations: 1.129 ms avg
average time for 10000 concatenations: 1.727 ms avg
average time for 10000 concatenations: 2.302 ms avg
Created string of length:1950000 in 60279 ms
[str1.append(str2)]
average time for 10000 concatenations: 0.002 ms avg
average time for 10000 concatenations: 0.002 ms avg
average time for 10000 concatenations: 0.002 ms avg
average time for 10000 concatenations: 0.002 ms avg
average time for 10000 concatenations: 0.002 ms avg
Created string of length:1950000 in 100 ms
随着字符串长度的增加,+=和.concat的连接时间也会增加,后者效率更高,但仍然是非常量
这就是绝对需要StringBuilder的地方。
附注:我不认为什么时候在Java中使用StringBuilder是一个真正的复制。
这个问题讨论的是toString(),它在大多数情况下不会执行大字符串的连接。
2019年更新
自java8时代以来,情况发生了一些变化。现在看来(java13), +=的连接时间实际上与str.concat()相同。但是StringBuilder的连接时间仍然是固定的。(上面的原始帖子略有编辑,添加了更多详细的输出)
java bytecode version:13
java.version: 13.0.1
[str1.concat(str2)]
average time for 10000 concatenations: 0.047 ms avg
average time for 10000 concatenations: 0.1 ms avg
average time for 10000 concatenations: 0.17 ms avg
average time for 10000 concatenations: 0.255 ms avg
average time for 10000 concatenations: 0.336 ms avg
Created string of length:1950000 in 9147 ms
[str1+=str2]
average time for 10000 concatenations: 0.037 ms avg
average time for 10000 concatenations: 0.097 ms avg
average time for 10000 concatenations: 0.249 ms avg
average time for 10000 concatenations: 0.298 ms avg
average time for 10000 concatenations: 0.326 ms avg
Created string of length:1950000 in 10191 ms
[str1.append(str2)]
average time for 10000 concatenations: 0.001 ms avg
average time for 10000 concatenations: 0.001 ms avg
average time for 10000 concatenations: 0.001 ms avg
average time for 10000 concatenations: 0.001 ms avg
average time for 10000 concatenations: 0.001 ms avg
Created string of length:1950000 in 43 ms
值得注意的还有bytecode:8/java。与bytecode:8/java.version:8相比,Version:13组合具有良好的性能优势
值得一提的是,正如@ZhekaKozlov指出的那样,
自Java 9以来,+更快,除非JVM不知道如何优化它(例如,循环中的连接)。
我检查了以下代码的字节码(在Java 17中):
public class StringBM {
public String toStringPlus(String a) {
return "{a:" + a + ", b:" + ", c: " + "}";
}
public String toStringBuilder(String a) {
StringBuilder sb = new StringBuilder(100);
return sb.append("{a:").append(a)
.append(", b:")
.append(", c:")
.append("}")
.toString();
}
}
For toStringPlus:
0: aload_1
1: invokedynamic #7, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
6: areturn
for toStringBuilder:
0: new #11 // class java/lang/StringBuilder
3: dup
4: bipush 100
6: invokespecial #13 // Method java/lang/StringBuilder."<init>":(I)V
9: astore_2
10: aload_2
11: ldc #16 // String {a:
13: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
16: aload_1
17: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: ldc #22 // String , b:
22: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: ldc #24 // String , c:
27: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: ldc #26 // String }
32: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: invokevirtual #28 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: areturn
+版本简单地调用动态函数makeConcatWithConstants和并传入方法参数{a:\u0001, b:, c:} (\u0001是参数占位符)。
而StringBuilder版本必须以“诚实”的方式来实现。
我想我们可以看到为什么+现在更快了。
我喜欢:
String.format( "{a: %s, b: %s, c: %s}", a, b, c );
...因为它简短易读。
我不会为速度而优化它,除非您在重复次数非常高的循环中使用它,并测量了性能差异。
我同意,如果必须输出大量参数,这个表单可能会令人困惑(就像其中一个评论所说的那样)。在这种情况下,我将切换到更可读的形式(可能使用apache-commons的ToStringBuilder -取自matt b的答案),并再次忽略性能。