看到網上有人已經做過對比,並且貼出了代碼,然后我運行了之后發現跟我分析的結論差距很大。發現他的代碼有個問題,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。
