三者就使用層面的簡略分析:
三者就字符串拼接方面運行速度快慢爲:StringBuilder > StringBuffer > String。
線程安全上:StringBuilder是線程不安全的,而StringBuffer是線程安全的,原因StringBuffer中使用了很多synchronized。
小結:(1)如果要操作少量的數據用 String;
(2)多線程操作字符串緩衝區下操作大量數據 StringBuffer;
(3)單線程操作字符串緩衝區下操作大量數據 StringBuilder;
三者的主要繼承關係:都實現了Serializable(略)
三者字符串可變與不可變問題:
String是不可變長度的字符串,
StringBuffer是可變長度的字符串,
StringBuilder是效率更高的可變長度的字符串。
從源碼中分析(略):
String:
String 中用了final來修飾value爲不可變的。
先提出一個問題:string是final修飾的是不可變的,爲何利用"+"後,返回了一個拼接後改變的字符串?前後不矛盾?詳細請看下個 string的"+"問題 的分析。
StringBuffer:
分析:可以看見只要創建StringBuffer()對象,至少也分配了16個字符的空間。
這裏的value沒有被final修飾
*String創建對象問題:
//例題四
String s5="abc"; //創建1個對象abc
String s6="abc"+"de"; //創建2個對象de,abcde
String s7="abcde"; //未創建對象
System.out.println(s6==s7);//true
//面試(重點重點):
//例題五
//題目:程序依次執行分別創建了多少新對象?
String a1=new String("我");
String a2="我";
String a3=new String("我");
/* 答案:從上到下依次執行程序,
a1創建2個對象
a2未創建對象
a3創建1個對象
解析:
a1:常量池中創建了"我",堆中創建了一個String類的對象。
(說明:new創建String對象時,需要傳入"我"這個字符串,因此會首先去常量池中尋找"我",因爲沒有,所以就在常量池中創建了"我"這個字符串及對象)
a2 :直接引用a1在常量池中創建的"我"。
a3:同理,只創建了一個新的String類對象*/
*(重點)String典型的“+”問題:
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String c = b + 2;
String f ="hello"+"2";
String d = "hello";
String e = d + 2;
String g = d + 2;
//第一類問題:直接字符串拼接
System.out.println(a == c);// true
System.out.println(a == f);// true
//第二類問題:字符串變量+字符串
System.out.println(a == e);// false
System.out.println(e == g);// false
}
解析:
a==c : 由於final的修飾,b在編譯期間就已經是一個確定值!由於各變量都爲確定值,因此b+2="hello"+"2"="hello2",注意這是在編譯時完成的(自動對代碼的優化),拼接好後由於常量池中已經存在"hello2"的字符串,因此不會創建新的字符串,最後c即指向"hello2",實際直接指向的是常量池的"hello2",a最早也指向常量池的"hello2",因此 a、c指向同一個地址。
a==f:同理。
a==e:編譯期間不知道d是什麼,會自動創建了一個StringBuffer對象,依次append(d).append("2"),最後toString給e。a指向的是常量池,而e指向的是對象及是指向的堆!因此a、e指向的是兩個地方,肯定不可能相等。
超級經典的例子讓你明白橫線上的問題:
String s=null;
s=s+"abc";
System.out.println(s);//nullabc
e==g:如果你看明白了a==e那這裏就太簡單了,因爲創建了兩個不同的StringBuffer對象,e、g分別指向這兩個對象!因此地址肯定不等!
*StringBuffer擴容問題:
StringBuffer初始化及擴容機制
1.StringBuffer()的初始容量可以容納16個字符,當該對象的實體存放的字符的長度大於16時,實體容量就自動增加。StringBuffer對象可以通過length()方法獲取實體中存放的字符序列長度,通過capacity()方法來獲取當前實體的實際容量。
2.StringBuffer(int size)可以指定分配給該對象的實體的初始容量參數爲參數size指定的字符個數。當該對象的實體存放的字符序列的長度大於size個字符時,實體的容量就自動的增加。以便存放所增加的字符。
3.StringBuffer(String s)可以指定給對象的實體的初始容量爲參數字符串s的長度額外再加16個字符。當該對象的實體存放的字符序列長度大於size個字符時,實體的容量自動的增加,以便存放所增加的字符。
擴容算法:
使用append()方法在字符串後面追加東西的時候,如果長度超過了該字符串存儲空間大小了就需要進行擴容:構建新的存儲空間更大的字符串,將久的複製過去;
再進行字符串append添加的時候,會先計算添加後字符串大小,傳入一個方法:ensureCapacityInternal 這個方法進行是否擴容的判斷,需要擴容就調用expandCapacity方法進行擴容
嘗試將新容量擴爲大小變成2倍+2 if 判斷一下 容量如果不夠,直接擴充到需要的容量大小。