java String、String.concat和StringBuilder性能对比
看到网上有人已经做过对比,并且贴出了代码,然后我运行了之后发现跟我分析的结论差距很大。发现他的代码有个问题,UUID.randomUUID() 首次调用耗时会很高,这个耗时被计算给了String,这对String是不公平的。
原始代码参见:http://www.codes51.com/article/detail_99554.html
修改后的测试代码如下:
import java.util.Date; import java.util.UUID; public class StringTest { public static void main(String[] args) { int testLength = 10000; String[] arr = new String[2]; String str = ""; Date start = new Date(); String testStr = UUID.randomUUID().toString(); System.out.println("首次生成randomUUID耗时:" + (new Date().getTime() - start.getTime())); start = new Date(); for (int i = 0; i < testLength; i++) { testStr = UUID.randomUUID().toString(); } System.out.println("非首次生成randomUUID " + testLength + "次耗时:" + (new Date().getTime() - start.getTime())); start = new Date(); for (int i = 0; i < testLength; i++) { str = testStr + testStr; } System.out.println("String 拼接测试,测试长度" + testLength + ",测试字符串数组长度" + arr.length + ",完成时间" + (new Date().getTime() - start.getTime())); start = new Date(); for (int i = 0; i < testLength; i++) { str = testStr.concat(testStr); } System.out.println("String.concat 拼接测试,测试长度" + testLength + ",测试字符串数组长度" + arr.length + ",完成时间" + (new Date().getTime() - start.getTime())); start = new Date(); StringBuilder sb; for (int i = 0; i < testLength; i++) { str = ""; sb = new StringBuilder(); for (int j = 0; j < arr.length; j++) { sb.append(testStr); } str = sb.toString(); } System.out.println("StringBuilder 拼接测试,测试长度" + testLength + ",测试字符串数组长度" + arr.length + ",完成时间" + (new Date().getTime() - start.getTime())); } }
测试结果:
1. 测试字符串数组长度10
首次生成randomUUID耗时:290
非首次生成randomUUID 10000次耗时:44
String 拼接测试,测试长度10000,测试字符串数组长度10,完成时间14
String 使用循环 拼接测试,测试长度10000,测试字符串数组长度10,完成时间66
String.concat 拼接测试,测试长度10000,测试字符串数组长度10,完成时间14
StringBuilder 拼接测试,测试长度10000,测试字符串数组长度10,完成时间14
2. 测试字符串数组长度5
首次生成randomUUID耗时:287
非首次生成randomUUID 10000次耗时:48
String 拼接测试,测试长度10000,测试字符串数组长度5,完成时间11
String 使用循环 拼接测试,测试长度10000,测试字符串数组长度5,完成时间20
String.concat 拼接测试,测试长度10000,测试字符串数组长度5,完成时间9
StringBuilder 拼接测试,测试长度10000,测试字符串数组长度5,完成时间10
3. 测试字符串数组长度3
首次生成randomUUID耗时:308
非首次生成randomUUID 10000次耗时:35
String 拼接测试,测试长度10000,测试字符串数组长度3,完成时间10
String 使用循环 拼接测试,测试长度10000,测试字符串数组长度3,完成时间21
String.concat 拼接测试,测试长度10000,测试字符串数组长度3,完成时间6
StringBuilder 拼接测试,测试长度10000,测试字符串数组长度3,完成时间11
4. 测试字符串数组长度2
首次生成randomUUID耗时:298
非首次生成randomUUID 10000次耗时:70
String 拼接测试,测试长度10000,测试字符串数组长度2,完成时间10
String 使用循环 拼接测试,测试长度10000,测试字符串数组长度2,完成时间8
String.concat 拼接测试,测试长度10000,测试字符串数组长度2,完成时间3
StringBuilder 拼接测试,测试长度10000,测试字符串数组长度2,完成时间7
5. 测试字符串数组长度1
首次生成randomUUID耗时:278
非首次生成randomUUID 10000次耗时:71
String 拼接测试,测试长度10000,测试字符串数组长度1,完成时间1
String 使用循环 拼接测试,测试长度10000,测试字符串数组长度1,完成时间8
String.concat 拼接测试,测试长度10000,测试字符串数组长度1,完成时间3
StringBuilder 拼接测试,测试长度10000,测试字符串数组长度1,完成时间4
到此,可以看出,绝大多数情况下StringBuilder妥妥的比String 使用循环快,但是跟String直接相加差不多,String concat效率跟StringBuilder差不多,很多时候还要快一些,这些都是为什么呢?
javap -c StringTest.class 看看Java编译器都做了什么:
Compiled from "StringTest.java" public class StringTest { public StringTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: sipush 10000 3: istore_1 4: iconst_2 5: anewarray #2 // class java/lang/String 8: astore_2 9: ldc #3 // String 11: astore_3 12: new #4 // class java/util/Date 15: dup 16: invokespecial #5 // Method java/util/Date."<init>":()V 19: astore 4 21: invokestatic #6 // Method java/util/UUID.randomUUID:()Ljava/util/UUID; 24: invokevirtual #7 // Method java/util/UUID.toString:()Ljava/lang/String; 27: astore 5 29: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 32: new #9 // class java/lang/StringBuilder 35: dup 36: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V 39: ldc #11 // String 首次生成randomUUID耗时: 41: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 44: new #4 // class java/util/Date 47: dup 48: invokespecial #5 // Method java/util/Date."<init>":()V 51: invokevirtual #13 // Method java/util/Date.getTime:()J 54: aload 4 56: invokevirtual #13 // Method java/util/Date.getTime:()J 59: lsub 60: invokevirtual #14 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 63: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 66: invokevirtual #16 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 69: new #4 // class java/util/Date 72: dup 73: invokespecial #5 // Method java/util/Date."<init>":()V 76: astore 4 78: iconst_0 79: istore 6 81: iload 6 83: iload_1 84: if_icmpge 101 87: invokestatic #6 // Method java/util/UUID.randomUUID:()Ljava/util/UUID; 90: invokevirtual #7 // Method java/util/UUID.toString:()Ljava/lang/String; 93: astore 5 95: iinc 6, 1 98: goto 81 101: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 104: new #9 // class java/lang/StringBuilder 107: dup 108: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V 111: ldc #17 // String 非首次生成randomUUID 113: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 116: iload_1 117: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 120: ldc #19 // String 次耗时: 122: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 125: new #4 // class java/util/Date 128: dup 129: invokespecial #5 // Method java/util/Date."<init>":()V 132: invokevirtual #13 // Method java/util/Date.getTime:()J 135: aload 4 137: invokevirtual #13 // Method java/util/Date.getTime:()J 140: lsub 141: invokevirtual #14 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 144: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 147: invokevirtual #16 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 150: new #4 // class java/util/Date 153: dup 154: invokespecial #5 // Method java/util/Date."<init>":()V 157: astore 4 159: iconst_0 160: istore 6 162: iload 6 164: iload_1 165: if_icmpge 195 168: new #9 // class java/lang/StringBuilder 171: dup 172: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V 175: aload 5 177: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 180: aload 5 182: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 185: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 188: astore_3 189: iinc 6, 1 192: goto 162 195: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 198: new #9 // class java/lang/StringBuilder 201: dup 202: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V 205: ldc #20 // String String 拼接测试,测试长度 207: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 210: iload_1 211: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 214: ldc #21 // String ,测试字符串数组长度 216: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 219: aload_2 220: arraylength 221: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 224: ldc #22 // String ,完成时间 226: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 229: new #4 // class java/util/Date 232: dup 233: invokespecial #5 // Method java/util/Date."<init>":()V 236: invokevirtual #13 // Method java/util/Date.getTime:()J 239: aload 4 241: invokevirtual #13 // Method java/util/Date.getTime:()J 244: lsub 245: invokevirtual #14 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 248: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 251: invokevirtual #16 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 254: new #4 // class java/util/Date 257: dup 258: invokespecial #5 // Method java/util/Date."<init>":()V 261: astore 4 263: iconst_0 264: istore 6 266: iload 6 268: iload_1 269: if_icmpge 317 272: ldc #3 // String 274: astore_3 275: iconst_0 276: istore 7 278: iload 7 280: aload_2 281: arraylength 282: if_icmpge 311 285: new #9 // class java/lang/StringBuilder 288: dup 289: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V 292: aload_3 293: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 296: aload 5 298: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 301: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 304: astore_3 305: iinc 7, 1 308: goto 278 311: iinc 6, 1 314: goto 266 317: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 320: new #9 // class java/lang/StringBuilder 323: dup 324: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V 327: ldc #23 // String String 使用循环 拼接测试,测试长度 329: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 332: iload_1 333: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 336: ldc #21 // String ,测试字符串数组长度 338: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 341: aload_2 342: arraylength 343: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 346: ldc #22 // String ,完成时间 348: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 351: new #4 // class java/util/Date 354: dup 355: invokespecial #5 // Method java/util/Date."<init>":()V 358: invokevirtual #13 // Method java/util/Date.getTime:()J 361: aload 4 363: invokevirtual #13 // Method java/util/Date.getTime:()J 366: lsub 367: invokevirtual #14 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 370: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 373: invokevirtual #16 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 376: new #4 // class java/util/Date 379: dup 380: invokespecial #5 // Method java/util/Date."<init>":()V 383: astore 4 385: iconst_0 386: istore 6 388: iload 6 390: iload_1 391: if_icmpge 408 394: aload 5 396: aload 5 398: invokevirtual #24 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String; 401: astore_3 402: iinc 6, 1 405: goto 388 408: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 411: new #9 // class java/lang/StringBuilder 414: dup 415: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V 418: ldc #25 // String String.concat 拼接测试,测试长度 420: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 423: iload_1 424: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 427: ldc #21 // String ,测试字符串数组长度 429: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 432: aload_2 433: arraylength 434: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 437: ldc #22 // String ,完成时间 439: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 442: new #4 // class java/util/Date 445: dup 446: invokespecial #5 // Method java/util/Date."<init>":()V 449: invokevirtual #13 // Method java/util/Date.getTime:()J 452: aload 4 454: invokevirtual #13 // Method java/util/Date.getTime:()J 457: lsub 458: invokevirtual #14 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 461: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 464: invokevirtual #16 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 467: new #4 // class java/util/Date 470: dup 471: invokespecial #5 // Method java/util/Date."<init>":()V 474: astore 4 476: iconst_0 477: istore 7 479: iload 7 481: iload_1 482: if_icmpge 533 485: ldc #3 // String 487: astore_3 488: new #9 // class java/lang/StringBuilder 491: dup 492: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V 495: astore 6 497: iconst_0 498: istore 8 500: iload 8 502: aload_2 503: arraylength 504: if_icmpge 521 507: aload 6 509: aload 5 511: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 514: pop 515: iinc 8, 1 518: goto 500 521: aload 6 523: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 526: astore_3 527: iinc 7, 1 530: goto 479 533: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 536: new #9 // class java/lang/StringBuilder 539: dup 540: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V 543: ldc #26 // String StringBuilder 拼接测试,测试长度 545: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 548: iload_1 549: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 552: ldc #21 // String ,测试字符串数组长度 554: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 557: aload_2 558: arraylength 559: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 562: ldc #22 // String ,完成时间 564: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 567: new #4 // class java/util/Date 570: dup 571: invokespecial #5 // Method java/util/Date."<init>":()V 574: invokevirtual #13 // Method java/util/Date.getTime:()J 577: aload 4 579: invokevirtual #13 // Method java/util/Date.getTime:()J 582: lsub 583: invokevirtual #14 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 586: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 589: invokevirtual #16 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 592: return }
String直接相加已经都被编译器优化成StringBuilder了,只是循环里优化的不太合理。所以,String相加快还是StringBuilder快,其实只是StringBuilder调用方式对比。。。
String.concat为什么快?
看看jdk1.8里这个方法的源代码就知道了:
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
简单粗暴,直接Arrays.copyOf,直接内存复制,这根StringBuilder原理类似,但是它不用初始化StringBuilder对象,只是每次concat都会创建一个新的String对象,所以在有些情况下它比StringBuilder要快一点。
结论:简单场景里,直接用+好了,反正编译器默认会优化成StringBuilder,毕竟对一般人来说加号可读性高一点。但是在循环中使用或者是比较复杂的应用场景里,还是尽量自己直接用StringBuilder或String concat。