1. 程式人生 > >String、StringBuilder和StringBuffer的區別

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指向的是同一個物件。