目錄
- 1 String不可變性
- 2 不可變的好處
- 3 String+和StringBuilder效率差異
- 4 String, StringBuffer and StringBuilder
- 5 String與JVM記憶體管理
- 6 String api方法
1 String不可變性
- String類被宣告為 final,因此它不可被繼承,
- 內部使用char陣列存盤資料,該陣列被宣告為final,這意味著value陣列初始化之后就不能再指向其它陣列,
- String內部沒有改變value陣列的方法
- String類中所有修改String值的方法,如果內容沒有改變,則回傳原來的String物件參考,如果改變了,創建了一個全新的String物件,包含修改后的字串內容,最初的String物件沒有任何改變,(目的:節約存盤空間、避免額外的開銷)
//String的類宣告以及value欄位代碼:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[]; //字符陣列存盤String的內容
/** Cache the hash code for the string */
private int hash; // Default to 0
}
不可變的驗證分析:
public class Immutable {
public static String upcase(String s) {
return s.toUpperCase();
}
public static void main(String[] args) {
String q = "howdy";
System.out.println(q); // howdy
String qq = upcase(q);
System.out.println(qq); // HOWDY
System.out.println(q); // howdy
}
} /* 輸出:
howdy
HOWDY
howdy
*///:~
- 當把q傳給upcase0方法時,實際傳遞的是參考的一個拷貝,
- upcase0方法中,傳入參考s,只有upcase0運行的時候,區域參考s才存在,一旦upcase0運行結束,s就消失,upcaseO的回傳值是最終結果的參考,
- 綜上,upcase()回傳的參考已經指向了一個新的物件,而原本的q則還在原地,
延伸結論:
String物件作為方法的引數時,都會復制一份參考,引數傳遞是參考的拷貝
2 不可變的好處
1. 可以快取 hash 值
String的hash值經常被使用,例如String用做HashMap的key,不可變的特性可以使得hash值也不可變,因此只需要進行一次計算,
2. String Pool 的需要
如果一個String物件已經被創建過了,那么就會從 String Pool 中取得參考,只有String是不可變的,才可能使用 String Pool,
3. 執行緒安全
String不可變性天生具備執行緒安全,可以在多個執行緒中安全地使用,
3 String+和StringBuilder效率差異
String使用“+”表示字串拼接
先說結論:
- “+”操作,javac編譯器會自動優化為StringBuilder.append() 呼叫,
- StringBuilder要比“+”操作高效
- 涉及回圈追加的,手動創建StringBuilder物件操作比“+”操作編譯器優化,更高效
驗證:
public class StringBuilderTest {
public static void main(String[] args) {
String s1 = "ABC";
String s2 = "123";
String result = s1+s2;
System.out.println(result);
}
}
編譯并查看位元組碼:javap -verbose StringBuilderTest.class

執行程序:
呼叫了2次append()方法,最后呼叫StringBuilder.toString()回傳最終結果
為什么StringBuilder要比+高效?
- +操作,按照:每次追加都創建新的String物件,把字符加入value陣列中,這里產生一次物件創建操作,以及對應的垃圾回收
- StringBuilder的底層陣列value也是用到了char[],但它沒有宣告為final,故它可變,所以追加內容時不用創建新的陣列,而是直接修改value
- StringBuilder比+省去String物件創建以及垃圾回收的開銷,因此效率更高,
原始碼追溯:
//StringBuilder.append()
@Override
public StringBuilder append(char c) {
super.append(c);
return this;
}
// 父類 AbstractStringBuilder.append()
@Override
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c;
return this;
}
//AbstractStringBuilder value 欄位
abstract class AbstractStringBuilder implements Appendable, CharSequence {
//The value is used for character storage.
char[] value; // 沒有宣告為final,因此value可變
}
手動實作StringBuilder物件操作比編譯器自行優化,更高效:
- 通過位元組碼分析可知(我這里省去了,可以自己實作驗證):回圈部分的代碼更簡短、更簡單,而且它只生成了一個StringBuilder物件,
- 顯式地創建StringBuilder還允許你預先為其指定大小,如果你已經知道最終的字串大概有多長,那預先指定StringBuilder的大小可以避免多次重新分配緩沖,
當你為一個類撰寫toString方法時,如果字串操作比較簡單,那就可以信賴編譯
器,它會為你合理地構造最終的字串結果,但是,如果你要在toString0方法中使用回圈,那
么最好自己創建一個StringBuilder物件來實作,
4 String, StringBuffer and StringBuilder
可變性
- String 不可變
- StringBuffer和StringBuilder可變
執行緒安全
- String不可變,因此是執行緒安全的
- StringBuilder 不是執行緒安全的
- StringBuffer 是執行緒安全的,內部使用synchronized進行同步
效率
- 如果要操作少量的資料用String
- 單執行緒環境且字串緩沖區涉及大量資料 StringBuilder
- 多執行緒環境且字串緩沖區涉及大量資料 StringBuffer
5 String與JVM記憶體管理
一、引入字串常量池
- Javac編譯后,位元組碼檔案中有一塊區域:常量池,存盤了包括類中宣告的字串常量值等字面量
- 運行時,JVM開辟實際記憶體空間:字串常量值寫入了字串常量池,屬于方法區的一部分,
案例1:
String s1 = "win";
String s2 = "win";
System.out.println(s1==s2);
//輸出結果:true
//參考 s1 s2 的值等于win在字串常量池的地址值
結論:
參考 s1 s2 的值等于win在字串常量池的地址值
分析位元組碼的執行程序:

案例2:
public class StringPool {
public static void main(String[] args) {
String s3 = new String("win");
String s4 = new String("win");
System.out.println(s3==s4);//false
}
}
結論:
通過new運算子創建的字串物件不指向字串池中的任何物件,
位元組碼分析:

綜上:
public class StringPool {
public static void main(String[] args) {
String s1 = "win";
String s2 = "win";
String s3 = new String("win");
String s4 = new String("win");
System.out.println(s1==s2);//true
System.out.println(s1==s3);//false
System.out.println(s3==s4);//false
}
}
6 String api方法
從這個表中可以看出,當需要改變字串的內容時,String類的方法都會回傳一個新的String物件,同時,如果內容沒有發生改變,String的方法只是回傳指向原物件的參考而已,這可以節約存盤空間以及避免額外的開銷,


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/540731.html
標籤:其他
