看這篇部落格之前,假設你看過http://blog.csdn.net/androidbluetooth/article/details/6525146
共用兩篇好文章:
String中三種加法的區別
JAVA的確是一種令程式員陷入兩難境地的言語, 確切的說是思想.
它提供了如此豐富的library,讓程式員能夠很容易的寫出功能強大的程式. 同時它也封裝了如此多的細節, 讓程式員能夠輕易的寫出很拙略的程式.
它所代表的object指向思想設計之初是為了把程式員從紛繁複雜的編程細節中解放出來,來達到對已有程式的利用.
但是在編程 實際上又要求程式員瞭解其實現的細節而避免寫出因調用已有library不正引起的performance慢的,耗費系統資源多的程式.
通常成為一個優秀的JAVA程式員需要較長時間的經驗積累,包括從程式的tuning中,或從其他有經驗的程式員口中,才知道一定功能需要怎樣實現,在程式中需要避免那些問題.但這往往是比較片面的,知其然而不知其所以然.我想大多數程式員都有類似的經曆,舉一個簡單的例子,下面有三種對String的累加操作.
① String tmp = "a" + "b" + "c"; ② String tmp = null; tmp+= "a"; tmp+= "b"; tmp+= "c"; ③ String tmp = null; StringBuffer buf = new StringBuffer(); buf.append("a"); buf.append("b"); buf.append("c"); tmp = buf.toString();
有些JAVA程式員在任意的用這三種方法的任一種,無視它們的區別.有 些程式員知道第三種方法好一直在用而不知其為什麼好,以至於作為經驗教條的傳授給JAVA新手.真正的答案是什麼呢?讓我們揭開JAVA String Class和StringBuffer Class的封裝面紗看看它的內部實現.在JAVA中的String Class是一個不可變類,所有對一個Sting Object的改變都回導致一個新的String Object的產生.那麼對tmp+=a中+符號的實現呢?如果你注意一下StringBuffer的Javadoc會發現.JDK對它的實現是
tmp=(new StringBuffer().append(tmp).append("a")).toSting();
這樣我們發現②這種方法在隱性產生了一個StringBuffer Object和一個String Object 再乘3就是六個object 的資源耗費.(還不包括String Class 和StringBuffer Class內部使用的Char[])而方法③只用了兩個.也許有些程式員會對這些耗費不以為然,的確也是,不過在一些情境下它會成為perfamence的瓶頸.再回頭看看①這種方法,它會被JAVA編譯器編譯為
tmp=(new StringBuffer().append("a").append("b").append("c")).to String();
我們發現做的和③方法是同樣的事.
對於方式①,Java編譯器在編譯期間就已經最佳化過。
java編譯器對string常量運算式的處理和最佳化
首先把問題擺出來,先看這個代碼
String a = "ab"; String b = "a" + "b"; System.out.println((a == b));
列印結果會是什嗎?類似這樣的問題,有人考過我,我也拿來考過別人(蠻好玩的,大家也可以拿來問人玩),一般答案會是以下幾種:
1.true
"a" + "b" 的結果就是"ab",這樣a,b都是"ab"了,內容一樣所以"相等",結果true
一般java新人如是答。
2.false
"a" + "a"會產生新的對象"aa",但是這個對象和String a = "ab";不同,(a == b)是比較對象引用,因此不相等,結果false ,對java的String有一定瞭解的通常這樣回答。
3.true
String a = "ab";建立了新的對象"ab"; 再執行String b = "a" + "b";結果b="ab",這裡沒有建立新的對象,而是從JVM字串常量池中擷取之前已經存在的"ab"對象。因此a,b具有對同一個string對象 的引用,兩個引用相等,結果true.
能回答出這個答案的,基本已經是高手了,對java中的string機制比較瞭解。
很遺憾,這個答案,是不夠準確的。或者說,根本沒有運行時計算b = "a" + "b";這個操作.實際上運行時只有String b = "ab";
3的觀點適合解釋以下情況:
String a = "ab"; String b = "ab"; System.out.println((a == b));
如果String b = "a" + "b";是在運行期執行,則3的觀點是無法解釋的。運行期的兩個string相加,會產生新的對象的。(本文後面對此有解釋)
4.true
下面是我的回答:編譯最佳化+ 3的處理方式 = 最後的true
String b = "a" + "b";//編譯器將這個"a" + "b"作為常量運算式,在編譯時間進行最佳化,直接取結果"ab",這樣這個問題退化 String a = "ab"; String b = "ab"; System.out.println((a == b));
然後根據3的解釋,得到結果true
這裡有一個疑問就是String不是基本類型,像
int secondsOfDay = 24 * 60 * 60;
這樣的運算式是常量運算式,編譯器在編譯時間直接計算容易理解,而"a" + "b" 這樣的運算式,string是對象不是基本類型,編譯器會把它當成常量運算式來最佳化嗎?
下面簡單證明我的推斷,首先編譯這個類:
public class Test { private String a = "aa"; }
複製class檔案備用,然後修改為
public class Test { private String a = "a" + "a"; }
再次編譯,用ue之類的文字編輯器開啟,察看二進位內容,可以發現,兩個class檔案完全一致,連一個位元組都不差.
ok,真相大白了.根本不存在運行期的處理String b = "a" + "b";這樣的代碼的問題,編譯時間就直接最佳化掉了。
下面進一步探討,什麼樣的string + 運算式會被編譯器當成常量運算式?
String b = "a" + "b";
這個String + String被正式是ok的,那麼string + 基本類型呢?
String a = "a1"; String b = "a" + 1;System.out.println((a == b)); //result = true String a = "atrue"; String b = "a" + true; System.out.println((a == b)); //result = true String a = "a3.4"; String b = "a" + 3.4; System.out.println((a == b)); //result = true
可見編譯器對string + 基本類型是當成常量運算式直接求值來最佳化的。
再注意看這裡的string都是"**"這樣的,我們換成變數來試試:
String a = "ab"; String bb = "b"; String b = "a" + bb; System.out.println((a == b)); //result = false
這個好理解,"a" + bb中的bb是變數,不能進行最佳化。這裡很很好的解釋了為什麼3的觀點不正確,如果String+String的操作是在運行時進行的,則會產生新的對象,而不是直接從jvm的string池中擷取。
再修改一下,把bb作為常量變數:
String a = "ab"; final String bb = "b"; String b = "a" + bb; System.out.println((a == b)); //result = true
竟然又是true,編譯器的最佳化好厲害啊,呵呵,考慮下面這種情況:
String a = "ab"; final String bb = getBB(); String b = "a" + bb; System.out.println((a == b)); //result = false private static String getBB() { return "b"; }
看來java(包括編譯器和jvm)對string的最佳化,真的是到了極點了,string這個所謂的"對象",完全不可以看成一般的對象,java對string的處理近乎於基本類型,最大限度的最佳化了幾乎能最佳化的地方。
感謝:
http://webservices.ctocio.com.cn/wsare/440/8134940.shtml
http://www.cublog.cn/u2/85805/showart_1842950.html