1. 程式人生 > >Java字符串連接的多種實現方法及效率對比

Java字符串連接的多種實現方法及效率對比

nan style time net println 毫秒 修改 nbsp 多種實現

JDK 1.8(Java 8)裏新增String.join()方法用於字符串連接。本文基於《Java實現String.join()和效率比較》一文,分析和比較四種自定義實現與String.join()方法的效率,並糾正原文的一些錯誤。

代碼示例如下:

 1 public class Test {
 2     public static void main(String[] args) {
 3         String[] strOri = {"a","b","c","d","e","f","g","h"}; //同new string[]{"a","b","c","d","e","f","g","h"}
4 String strRes = ""; int loops = 100000; 5 6 Date date = new Date(); 7 8 for(int i = 0; i < loops; i++) {strRes = join1(strOri, ":");} date = recordTime(date, 1); //1 9 for(int i = 0; i < loops; i++) {strRes = join2(strOri, ":");} date = recordTime(date, 2);
10 for(int i = 0; i < loops; i++) {strRes = join3(strOri, ":");} date = recordTime(date, 3); 11 for(int i = 0; i < loops; i++) {strRes = join4(strOri, ":");} date = recordTime(date, 4); 12 for(int i = 0; i < loops; i++) {strRes = join5(strOri, ":");} date = recordTime(date, 5);
13 14 long startTime = System.currentTimeMillis(); //2 15 for(int i = 0; i < loops; i++) {strRes = join5(strOri, ":");} 16 long endTime = System.currentTimeMillis(); System.out.println("5c:{" + strRes + "} costs " + (endTime-startTime) + "ms"); 17 18 startTime = System.nanoTime(); 19 for(int i = 0; i < loops; i++) {strRes = join5(strOri, ":");} 20 endTime = System.nanoTime(); System.out.println("5n:{" + strRes + "} costs " + (endTime-startTime) + "ns"); 21 } 22 23 private static void recordTime_Wrong(Date date, int no) { 24 System.out.println(no + ": costs " + (new Date().getTime()-date.getTime()) + "ms"); 25 date = new Date(); 26 } 27 private static Date recordTime(Date date, int no) { 28 System.out.println(no + ": costs " + (new Date().getTime()-date.getTime()) + "ms"); 29 return new Date(); 30 } 31 32 private static String join1(String[] strOri, String delimiter) { 33 StringBuffer sb = new StringBuffer(); //3 34 for(String s : strOri) { 35 sb.append(s+delimiter); //4 36 } 37 return sb.toString().substring(0, sb.toString().length()-1); 38 } 39 40 private static String join2(String[] strOri, String delimiter) { 41 StringBuffer sb = new StringBuffer(); 42 for(String s : strOri) { 43 sb.append(s+delimiter); 44 } 45 String s = sb.toString(); 46 return s.substring(0, s.length()-1); 47 } 48 49 private static String join3(String[] strOri, String delimiter) { 50 StringBuffer sb = new StringBuffer(); 51 for(int i = 0; i < strOri.length; i++) { 52 if (i != strOri.length-1) { 53 sb.append(strOri[i]+delimiter); 54 } else { 55 sb.append(strOri[i]); 56 } 57 } 58 return sb.toString(); 59 } 60 61 private static String join4(String[] strOri, String delimiter) { 62 StringBuilder stringBuilder = new StringBuilder(); 63 for (int i = 0; i < strOri.length-1; i++) { 64 stringBuilder.append(strOri[i]).append(delimiter); 65 } 66 stringBuilder.append(strOri[strOri.length-1]); 67 return stringBuilder.toString(); 68 } 69 70 private static String join5(String[] strOri, String delimiter) { 71 return String.join(delimiter, strOri); //5 72 } 73 }

選取三次運行輸出結果如下:

1: costs 930ms
2: costs 902ms
3: costs 637ms
4: costs 230ms
5: costs 364ms
5c:{a:b:c:d:e:f:g:h} costs 413ms
5n:{a:b:c:d:e:f:g:h} costs 286466296ns
1: costs 834ms
2: costs 788ms
3: costs 576ms
4: costs 248ms
5: costs 350ms
5c:{a:b:c:d:e:f:g:h} costs 384ms
5n:{a:b:c:d:e:f:g:h} costs 283256112ns
1: costs 774ms
2: costs 728ms
3: costs 605ms
4: costs 297ms
5: costs 417ms
5c:{a:b:c:d:e:f:g:h} costs 280ms
5n:{a:b:c:d:e:f:g:h} costs 279838638ns

可見,join4()執行最快,其次是join5()。join1()和join2()執行效率接近,前者調用兩次toString(),故效率略低。

總結如下:
1. 原文recordTime(即本文recordTime_Wrong)方法中,無法通過"date = new Date()"修改外部的date引用(根因詳見《java中的傳值與傳引用》)。這會導致每次調用recordTime()時,起始時間始終是"Date date = new Date()"獲得的對象(表現為join*耗時遞增)。
2. 查看Java源碼可知,new Date()其實就是調用System.currentTimeMillis():

1 public Date() {
2     this(System.currentTimeMillis()); //相當於Date(System.currentTimeMillis())
3 }

可以使用new Date().getTime()獲取當前時間戳(毫秒)。註意,該毫秒數一般以1970-01-01 00:00:00為參考點,但東八區要加上時區,即以1970-01-01 08:00:00為參考時間。此外,通過getTime()獲取毫秒數效率不如System.currentTimeMillis(),後者返回自1970年1月1日0時起的毫秒數。

System.nanoTime()的計時精度不保證一定高於System.currentTimeMillis(),但可保證數值遞增(後者相減時可能產生負值)。

若要對代碼進行更準確的計時,可參考《How do I write a correct micro-benchmark in Java?》一文。
3. StringBuffer對象是線程安全的,其方法都是同步的(synchronized)。臨時變量應使用StringBuilder(效率更高),避用StringBuffer。
4. 在循環內部,不要使用append(a+b)的形式,而應改為append(a).append(b)。
5. String.join()內部使用StringBuilder實現,因此join5()性能接近join4()。當然,String.join()的功能比join4()更多。

Java字符串連接的多種實現方法及效率對比