天天看點

String,StringBuffer, StringBuilder 的差別是什麼?String為什麼是不可變的?

本内容是我從各處轉載整理得來,是我的學習筆記,如有涉及到版權問題,請給我留言。

或者内容中有不正确的地方,也請各位大神能幫我指出更改,謝謝!

一、差別

1、String是字元串常量,而StringBuffer和StringBuilder是字元串變量。由String建立的字元内容是不可改變的,而由StringBuffer和StringBuidler建立的字元内容是可以改變的。

2、StringBuffer是線程安全的,而StringBuilder是非線程安全的。StringBuilder是從JDK 5開始,為StringBuffer類補充的一個單線程的等價類。我們在使用時應優先考慮使用StringBuilder,因為它支援StringBuffer的所有操作,但是因為它不執行同步,不會有線程安全帶來額外的系統消耗,是以速度更快。

二、String為什麼不可變

雖然String、StringBuffer和StringBuilder都是final類,它們生成的對象都是不可變的,而且它們内部也都是靠char數組實作的,但是不同之處在于,String類中定義的char數組是final的,而StringBuffer和StringBuilder都是繼承自AbstractStringBuilder類,它們的内部實作都是靠這個父類完成的,而這個父類中定義的char數組隻是一個普通是私有變量,可以用append追加。因為AbstractStringBuilder實作了Appendable接口。

三、為什麼String要設計成不可變

在Java中将String設計成不可變的是綜合考慮到各種因素的結果,想要了解這個問題,需要綜合記憶體,同步,資料結構以及安全等方面的考慮. 在下文中,我将為各種原因做一個小結。

1. 字元串常量池的需要

字元串常量池(String pool, String intern pool, String保留池) 是Java堆記憶體中一個特殊的存儲區域, 當建立一個String對象時,假如此字元串值已經存在于常量池中,則不會建立一個新的對象,而是引用已經存在的對象。

如下面的代碼所示,将會在堆記憶體中隻建立一個實際String對象.

[java]  view plain copy

  1. String s1 = "abcd";  
  2. String s2 = "abcd";  

示意圖如下所示:

String,StringBuffer, StringBuilder 的差別是什麼?String為什麼是不可變的?

圖1

假若字元串對象允許改變,那麼将會導緻各種邏輯錯誤,比如改變一個對象會影響到另一個獨立對象. 嚴格來說,這種常量池的思想,是一種優化手段.

請思考: 假若代碼如下所示,s1和s2還會指向同一個實際的String對象嗎?

[javascript]  view plain copy

  1. String s1= "ab" + "cd";  
  2. String s2= "abc" + "d";  

也許這個問題違反新手的直覺, 但是考慮到現代編譯器會進行正常的優化, 是以他們都會指向常量池中的同一個對象. 或者,你可以用  jd-gui  之類的工具檢視一下編譯後的class檔案.

2. 允許String對象緩存HashCode

Java中String對象的哈希碼被頻繁地使用, 比如在hashMap 等容器中。

字元串不變性保證了hash碼的唯一性,是以可以放心地進行緩存.這也是一種性能優化手段,意味着不必每次都去計算新的哈希碼. 在String類的定義中有如下代碼:

[javascript]  view plain copy

  1. private int hash;//用來緩存HashCode  

3. 安全性

String被許多的Java類(庫)用來當做參數,例如 網絡連接配接位址URL,檔案路徑path,還有反射機制所需要的String參數等, 假若String不是固定不變的,将會引起各種安全隐患。

假如有如下的代碼:

[javascript]  view plain copy

  1. boolean connect(string s){  
  2.     if (!isSecure(s)) {   
  3. throw new SecurityException();   
  4. }  
  5.     // 如果在其他地方可以修改String,那麼此處就會引起各種預料不到的問題/錯誤   
  6.     causeProblem(s);  
  7. }  

總體來說, String不可變的原因包括 設計考慮,效率優化問題,以及安全性這三大方面. 事實上,這也是Java面試中的許多 "為什麼" 的答案。

四、String類不可變性的好處

String是所有語言中最常用的一個類。我們知道在Java中,String是不可變的、final的。Java在運作時也儲存了一個字元串池(String pool),這使得String成為了一個特别的類。

String類不可變性的好處

1.隻有當字元串是不可變的,字元串池才有可能實作。字元串池的實作可以在運作時節約很多heap空間,因為不同的字元串變量都指向池中的同一個字元串。但如果字元串是可變的,那麼String interning将不能實作(譯者注:String interning是指對不同的字元串僅僅隻儲存一個,即不會儲存多個相同的字元串。),因為這樣的話,如果變量改變了它的值,那麼其它指向這個值的變量的值也會一起改變。

2.如果字元串是可變的,那麼會引起很嚴重的安全問題。譬如,資料庫的使用者名、密碼都是以字元串的形式傳入來獲得資料庫的連接配接,或者在socket程式設計中,主機名和端口都是以字元串的形式傳入。因為字元串是不可變的,是以它的值是不可改變的,否則黑客們可以鑽到空子,改變字元串指向的對象的值,造成安全漏洞。

3.因為字元串是不可變的,是以是多線程安全的,同一個字元串執行個體可以被多個線程共享。這樣便不用因為線程安全問題而使用同步。字元串自己便是線程安全的。

4.類加載器要用到字元串,不可變性提供了安全性,以便正确的類被加載。譬如你想加載java.sql.Connection類,而這個值被改成了myhacked.Connection,那麼會對你的資料庫造成不可知的破壞。

5.因為字元串是不可變的,是以在它建立的時候hashcode就被緩存了,不需要重新計算。這就使得字元串很适合作為Map中的鍵,字元串的處理速度要快過其它的鍵對象。這就是HashMap中的鍵往往都使用字元串。