String、StringBuilder和StringBuffer的區別
1 String、StringBuilder和StringBuffer的區別
String內部是通過char陣列來儲存資料的,類的操作方法substr、replace等都需要重新new一個新的char陣列來儲存,不會對原String物件產生影響。
String str1 = "hello world";這樣定義的字元常量儲存在常量池中,常量池中相同的字串只有一個備份。在棧上建立的多個引用可以指向相同的常量,只能更改引用指向不同的常量,不能修改引用指向的常量值。
String str2 = new String("hello world");字串物件是在堆區進行的,而在堆區進行物件生成的過程是不會去檢測該物件是否已經存在的。
StringBuilder比String的效率高,造成的記憶體垃圾少。StringBuffer效率和Stringbuilder效率差不多,StringBuffer是執行緒安全的。
1.1 迴圈累加效率
1.1.1 String迴圈累加
String string = "";
for(int i=0;i<10000;i++)
{
string += "hello";
}
從下面反編譯出的位元組碼檔案可以很清楚地看出:從第8行開始到第35行是整個迴圈的執行過程,並且每次迴圈會new出一個StringBuilder物件,然後進行append操作,最後通過toString方法返回String物件。也就是說這個迴圈執行完畢new出了10000個物件,試想一下,如果這些物件沒有被回收,會造成多大的記憶體資源浪費。從上面還可以看出:string+="hello"的操作事實上會自動被JVM優化成:
StringBuilder str = new StringBuilder(string);
str.append("hello");
str.toString();
1.1.2 StringBuilder迴圈累加
StringBuilder stringBuilder = new StringBuilder();
for(int i=0;i<10000;i++)
{
stringBuilder.append("hello");
}
for迴圈式從13行開始到27行結束,並且new操作只進行了一次,也就是說只生成了一個物件,append操作是在原有物件的基礎上進行的。因此在迴圈了10000次之後,這段程式碼所佔的資源要比String小得多。
1.1.3 StringBuffer迴圈累加
StringBuilder和StringBuffer類擁有的成員屬性以及成員方法基本相同,區別是StringBuffer類的成員方法前面多了一個關鍵字:synchronized,不用多說,這個關鍵字是在多執行緒訪問時起到安全保護作用的,也就是說StringBuffer是執行緒安全的。
1.2 效能測試
對三個類進行50000次迴圈累加效能測試。String 8998毫秒,StringBuilder 2毫秒,StringBuffer3毫秒。因為String需要多次new StringBuilder,所以需要消耗大量的時間和造成大量的記憶體垃圾。StringBuffer和StringBuilder差不多,因為StringBuffer執行緒安全,所以會比StringBuilder多1毫秒。當字串相加操作或者改動較少的情況下,建議使用 String str="hello"這種形式;當字串相加操作較多的情況下,建議使用StringBuilder,如果採用了多執行緒,則使用StringBuffer。
1.3 練習測試
(1)下面這段程式碼的輸出結果是什麼?
String a = "hello2"; String b = "hello" + 2; System.out.println((a == b));
輸出結果為:true。原因很簡單,"hello"+2在編譯期間就已經被優化成"hello2",因此在執行期間,變數a和變數b指向的是同一個物件。
(2)下面這段程式碼的輸出結果是什麼?
String a = "hello2"; String b = "hello"; String c = b + 2; System.out.println((a == c));
輸出結果為:false。由於有符號引用的存在,所以 String c = b + 2;不會在編譯期間被優化,不會把b+2當做字面常量來處理的,因此這種方式生成的物件事實上是儲存在堆上的。因此a和c指向的並不是同一個物件。javap -c得到的內容:
(3)下面這段程式碼的輸出結果是什麼?
String a = "hello2"; final String b = "hello"; String c = b + 2; System.out.println((a == c));
輸出結果為:true。對於被final修飾的變數,會在class檔案常量池中儲存一個副本,也就是說不會通過連線而進行訪問,對final變數的訪問在編譯期間都會直接被替代為真實的值。那麼String c = b + 2;在編譯期間就會被優化成:String c = "hello" + 2; 下圖是javap -c的內容:
總結:
對於String b=“hello”+2;這種常量相加,在編譯期就優化為常量,在常量池儲存。對於String b=a+2; 不會在編譯期間被優化,不會把b+2當做字面常量來處理的,實際是儲存在堆上面的,呼叫了一次new StringBuilder。
如果a的定義是final String a = "hello"; 對於被final修飾的變數,會在class檔案常量池中儲存一個副本,也就是說不會通過連線而進行訪問,對final變數的訪問在編譯期間都會直接被替代為真實的值。那麼String b= a + 2;在編譯期間就會被優化成:String b = "hello" + 2;
String a = "hello";String b = new
String("hello");String c = new
String("hello");String d = b.intern();
String.intern方法的使用。在String類中,intern方法是一個本地方法,在JAVA SE6之前,intern方法會在執行時常量池中查詢是否存在內容相同的字串,如果存在則返回指向該字串的引用,如果不存在,則會將該字串入池,並返回一個指向該字串的引用。因此,a和d指向的是同一個物件。