Java基礎類String學習分析

範德依彪發表於2022-12-26

1 String不可變性

  1. String類被宣告為 final,因此它不可被繼承。
  2. 內部使用char陣列儲存資料,該陣列被宣告為final,這意味著value陣列初始化之後就不能再指向其它陣列。
  3. String內部沒有改變value陣列的方法
  4. 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
    *///:~
  1. 當把q傳給upcase0方法時,實際傳遞的是引用的一個複製。
  2. upcase0方法中,傳入引用s,只有upcase0執行的時候,區域性引用s才存在。一旦upcase0執行結束,s就消失。upcaseO的返回值是最終結果的引用。
  3. 綜上,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使用“+”表示字串拼接

先說結論:

  1. “+”操作,javac編譯器會自動最佳化為StringBuilder.append() 呼叫。
  2. StringBuilder要比“+”操作高效
  3. 涉及迴圈追加的,手動建立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
Java基礎類String學習分析

執行過程:
呼叫了2次append()方法,最後呼叫StringBuilder.toString()返回最終結果

為什麼StringBuilder要比+高效?

  1. +操作,按照:每次追加都建立新的String物件,把字元加入value陣列中。這裡產生一次物件建立操作,以及對應的垃圾回收
  2. StringBuilder的底層陣列value也是用到了char[],但它沒有宣告為final,故它可變,所以追加內容時不用建立新的陣列,而是直接修改value
  3. 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物件操作比編譯器自行最佳化,更高效

  1. 透過位元組碼分析可知(我這裡省去了,可以自己實現驗證):迴圈部分的程式碼更簡短、更簡單,而且它只生成了一個StringBuilder物件
  2. 顯式地建立StringBuilder還允許你預先為其指定大小。如果你已經知道最終的字串大概有多長,那預先指定StringBuilder的大小可以避免多次重新分配緩衝
當你為一個類編寫toString方法時,如果字串操作比較簡單,那就可以信賴編譯
器,它會為你合理地構造最終的字串結果。但是,如果你要在toString0方法中使用迴圈,那
麼最好自己建立一個StringBuilder物件來實現。

4 String, StringBuffer and StringBuilder

可變性

  1. String 不可變
  2. StringBuffer和StringBuilder可變

執行緒安全

  1. String不可變,因此是執行緒安全的
  2. StringBuilder 不是執行緒安全的
  3. StringBuffer 是執行緒安全的,內部使用synchronized進行同步

效率

  1. 如果要操作少量的資料用String
  2. 單執行緒環境且字串緩衝區涉及大量資料 StringBuilder
  3. 多執行緒環境且字串緩衝區涉及大量資料 StringBuffer

5 String與JVM記憶體管理

一、引入字串常量池

  1. Javac編譯後,位元組碼檔案中有一塊區域:常量池,儲存了包括類中宣告的字串常量值等字面量
  2. 執行時,JVM開闢實際記憶體空間:字串常量值寫入了字串常量池,屬於方法區的一部分。

案例1

String s1 = "win";
String s2 = "win";
System.out.println(s1==s2);
//輸出結果:true
//引用 s1  s2 的值等於win在字串常量池的地址值

結論:
引用 s1 s2 的值等於win在字串常量池的地址值

分析位元組碼的執行過程:
Java基礎類String學習分析


案例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運算子建立的字串物件不指向字串池中的任何物件
位元組碼分析:
Java基礎類String學習分析

綜上:

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的方法只是返回指向原物件的引用而已。這可以節約儲存空間以及避免額外的開銷。
Java基礎類String學習分析
Java基礎類String學習分析

相關文章