我們來考慮一個關於java中String的問題: "abc" + '/'和"abc" + "/"的區別. 通過這個例子, 我們可以順便練習一下JDK工具中javap的用法, 原問題是這樣的:
把斜杠/當作字符或字符串有什麼區別呢?
一個是當作基本數據類型char,一個是對象String。具體有什麼區別呢?
當作字符效率會更高嗎?
String str = "abc" + '/';
和
String str = "abc" + "/";
1、編譯器優化
首先大家應該知道, 上面那兩句效果是一樣的, 因為編譯器會把上面那兩句都優化成下面的樣子:
String str = "abc/";
我們可以通過javap證明這一點. 關於javap, 可以參考《每個Java開發者都應該知道的5個JDK工具》. 我們首先創建一個類: StringOne, 在主方法中填入下面的代碼:
StringOne.javaString str1 = "abc" + '/';String str2 = "abc" + "/";System.out.println(str1 == str2);
編譯並運行, 輸出結果為true. 接下來, 該我們的javap登場了, 在命令行中輸入下面的命令:
javap -v -l StringOne.class > StringOne.s
然後查看生成的StringOne.s文件. 就會發現其中有這麼幾行
StringOne.s#2 = String #20 // abc/...#20 = Utf8 abc/...0: ldc #2 // String abc/2: astore_13: ldc #2 // String abc/5: astore_2
說明str1和str2都引用字符串"abc/".
2、使用javap分析差異
現在我們換一個問法, 下面的代碼中stringAddString和stringAddChar方法有什麼區別?
StringTwopublic static String stringAddString(String str1, String str2){ return str1 + str2;}public static String stringAddChar(String str, char ch){ return str + ch;}這次再使用javap進行反編譯, 生成文件的部分內容如下所示
StringTwo.spublic java.lang.String stringAddString(java.lang.String, java.lang.String); descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=3 0: new #2 // class java/lang/StringBuilder 3: dup 4: invokespecial #3 // Method java/lang/StringBuilder."< init>":()V 7: aload_1 8: invokevirtual #4 // Method java/lang/StringBuilder. append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 11: aload_2 12: invokevirtual #4 // Method java/lang/StringBuilder. append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: invokevirtual #5 // Method java/lang/StringBuilder. toString:()Ljava/lang/String; 18: areturnpublic java.lang.String stringAddChar(java.lang.String, char); descriptor: (Ljava/lang/String;C)Ljava/lang/String; flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=3 0: new #2 // class java/lang/StringBuilder 3: dup 4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V 7: aload_1 8: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 11: iload_2 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder; 15: invokevirtual #5 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 18: areturn
現在, 我們已經可以很清楚的看出這兩個方法執行的流程了:
stringAddString
stringAddChar的過程和stringAddString一樣, 只是在第二次調用append方法時stringAddString的參數是String類型, 而stringAddChar的參數是char類型.
3、StringBuilder類的append(char)方法和append(String)方法
這裡,我們直接查看源碼就好了(我的是jdk1.8.0_60附帶的源碼)。 注意,雖然文檔上顯示StringBuilder繼承自Object, 但是從源碼來看, 它是繼承自抽像類AbstractStringBuilder的。而且append方法是由AbstractStringBuilder實現的。
AbstractStringBuilder.java
public AbstractStringBuilder append(char c) { ensureCapacityInternal(count + 1); // 確保數組能夠容納count+1個字符value[count++] = c; return this;}public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); // 拷貝字符串中的字符數組到本對象的字符數組中count += len; return this;}剩下的就不再貼出來了。 String.getChars(int, int, char[], int)最終依賴於public static native void arraycopy(Object, int, Object, int, int)。也就是說有可能是C語言寫的,在拷貝大型數組時效率應該會比java寫的程序好一些。 那麼,現在說說我的理解:
從直接內存來說, 由於String中包含char數組, 而數組應該是有長度字段的, 同時String類還有一個int hash屬性, 再加上對象本身會佔用額外的內存存儲其他信息,所以字符串會多佔用一些內存. 但是如果字符串非常長, 那麼這些內存開銷差不多就可以忽略了; 而如果像"/"這種情況, 字符串比較(非常)短,那麼就很可能有許多個共享引用來分擔這些內存開銷, 那麼多餘的內存開銷還是可以忽略的.
從調用堆棧上, 由於這裡String只比char多了一兩層函數調用,所以如果不考慮函數調用開銷(包括時間和空間), 應該差不多;考慮函數調用開銷, 應該"abc" + '/'更好一些; 但是當需要連接若干個字符時(感覺這種情況應該更常見吧?), 由於使用char需要循環好多次才能完成連接, 調用的函數次數只會比使用String更多. 同時拷貝也不會比String直接拷貝一個數組更快. 所以這個時候就變成了"abc" + "/"吞吐量更大.
現在感覺這個問題像是在問: 讀寫文件時使用系統調用效率高, 還是使用標準函數庫中的IO庫效率高. 個人感覺, 雖然標準IO庫最後還得調用系統調用, 而且這之間會產生一些臨時變量, 以及更深層次的調用堆棧, 但是由於IO庫的緩衝等機制, 所以IO庫的吞吐量會更大, 而係統調用的實時性會好一些. 同樣, 雖然String類會多幾個字段, 有更深層次的函數堆棧, 但是由於緩存以及更直接的拷貝, 吞吐量應該會更好一些.
新的問題
從上面javap的反編譯代碼來看, 兩個String相加, 會變成向StringBuilder中append字符串. 那麼理論上, 下面哪段代碼的效率好呢?
String str1 = "abc" + "123"; // 1StringBuilder stringBuilder = new StringBuilder(); // 2stringBuilder.append("abc");stringBuilder.append("123");String str2 = stringBuilder.toString();這個問題就留給大家思考吧!
以上就是本文的全部內容,幫助大家更好的區分java中String+String和String+char,希望對大家的學習有所幫助。