天天看點

String、SrtingBuilder、StringBuffer詳解String、SrtingBuilder、StringBuffer詳解

轉載請注明連結:https://blog.csdn.net/feather_wch/article/details/82389184

本文包括如下内容:

  1. String的特點
  2. StringBuider的特點
  3. StringBuffer的特點
  4. 什麼是字元串緩存的intern機制
  5. 什麼是字元串排重
  6. 什麼是intrinsic機制
  7. Java 9中String的改進

String、SrtingBuilder、StringBuffer詳解

版本号:2018/9/5-1(16:16)

  • String、SrtingBuilder、StringBuffer詳解
    • 問題彙總
    • String
      • 常量池
      • intern
    • AbstractStringBuilder
      • StringBuffer
      • StringBuilder
    • 字元串緩存
      • 字元串排重
      • Intrinsic機制
    • Java9 Compact String
    • 知識擴充
      • 編譯和反編譯
    • 知識儲備
    • 參考資料

問題彙總

  1. 【☆】String包含哪些方面的知識?
    1. String、StringBuilder、StringBuffer的特點和差別。
    2. 字元串緩存的intern機制
    3. 字元串排重(JVM)
    4. Intrinsic機制
    5. JAVA9的Compat Strings
  2. String、StringBuffer、StringBuilder的差別
  3. String的特點
  4. String的immutabale特性有哪些優點呢?
  5. String的内部原理
  6. String比較的equals和==的差別
  7. String API的分類(12)
  8. String拼接的場景中是否一定要使用StringBuilder或者StringBuffer?
  9. String底層實作采用char導緻的問題?
  10. AbstractStringBuilder是什麼(2)
  11. AbstractStringBuilder的API分類(7)
  12. StringBuffer和StringBuilder的擴容問題(預設容量和性能損耗)
  13. StringBuffer的特點(3)
  14. StringBuffer的适用場景?
  15. StringBuilder的特點(4)
  16. StringBuilder的适用場景?
  17. 字元串重複的開銷問題
  18. Java 6開始提供的intern()的作用
  19. Java 6中intern的嚴重缺陷
  20. Java 6以後的字元串緩存的優化
  21. 字元串緩存大小?如何修改?
  22. Java6以後intern()的副作用
  23. Oracle JDK 8u20後,推出了字元串排重的新特性
  24. JDK 8 的字元串排重的功能如何開啟?(GC1)
  25. JVM内部的Intrinsic機制是幹什麼的?
  26. 字元串如何利用Intrinsic機制優化的?
  27. Java9中StringBuffer和StringBuilder底層的char[]數組都變更為byte[]數組
  28. Java9中的字元串引入了Compact Strings進行了哪些方面的修改?
  29. String是典型的immutable類,final修飾的類是否就是immutable的類?
  30. final的作用?(類、變量、方法)
  31. 如何去實作一個immutable類?
  32. 【☆】Java中對String的緩存機制?
    intern()、JDK8JVM層的字元串排重
  33. getBytes()和new String()采用的什麼編碼方式?
  34. JDK中String的hash值為什麼沒有采用final修飾,也沒有考慮hashcode()在多線程中會重複計算的問題?
  35. Java為了避免在系統中産生大量的String對象,引入了字元串常量池。
  36. 字元串常量池有什麼用?
  37. 所有的String都是字元串常量池?
  38. 【☆】建立字元串對象的兩種方式?
    1. 直接指派:

      String str = “bitch”;

    2. new方式建立
  39. new方式建立的String對象是否會采用字元串常量池?
  40. 為什麼StringBuilder、StringBuffer要給定初始值?
  41. String采用的不可變模式的優點?
  42. String常量池的優化機制?
  43. String str = new String(“AB”)是否還會涉及到常量池?如何驗證?
  44. String str = new String(“AB”)會建立幾個對象?
  45. 【☆】String str = “AB”會建立幾個對象?
    隻會建立一個對象。
  46. 如何列印對象的位址?
  47. 如何列印出String對象的位址?
  48. 如何通過string調用Object的toString
  49. intern在JDK1.6和JDK1.7的差別?

String

1、String、StringBuffer、StringBuilder的差別

String 特點 線程安全 性能
String 提供字元串相關功能。内部是final char[]數組。是典型的

immutable

類(final的class、final的字段)。
安全 性能低,内不會産生新的String對象。
StringBuffer 用于解決字元串拼接産生的中間對象的問題。繼承自

AbstractStringBuilder

内部是char[]數組
安全 性能稍低,采用synchronized進行加鎖。
StringBuilder 繼承自

AbstractStringBuilder

内部是char[]數組
線程不安全 性能高

2、String的特點(4)

  1. String是典型的

    immutable

    類(不可變的):

    修改String不會在原有的記憶體位址修改,而是重新指向一個新對象

  2. String用final修飾,不可以

    繼承

    :String本質是

    final的char[]數組

    ,是以

    char[]

    數組的記憶體位址不會被修改,而且

    String

    沒有對外暴露修改

    char[]數組

    的方法。
  3. String是線程安全的:因為其是

    immutable

    類,實作

    字元串常量池

  4. 頻繁的

    增删操作

    不建議使用

    String

  5. 操作不當會導緻大量臨時String對象。

3、String的immutabale特性有哪些優點呢?

  1. 性能安全
  2. 拷貝:不需要額外複制資料。
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
           

4、String的内部原理

  1. String

    内部是一個

    final修飾 char[]數組

    :名稱為

    value

  2. String的構造

    本質上就是對

    value數組

    賦初值的過程。
  3. String

    的其他API本質都是對

    value數組

    操作的過程。如:

    substring

    是通過

    數組的複制

    完成的,最終會

    傳回

    建立的

    String

    ,不會影響到原有的數組。

5、String比較的equals和==的差別

  1. astr.equals(bstr) 比較對象是否相等
  2. == 僅僅是比較首位址

6、String API的分類(12)

  1. 構造方法
  2. 字元串長度:length
  3. 字元串某一位置:charAt
  4. 提取子串:substring
  5. 字元串比較:compareTo、compareToIgnore、equals、equalsIgnoreCase
  6. 字元串的連接配接:concat
  7. 字元串的查找:indexOf、lastIndexOf
  8. 字元串的替換:replace
  9. 前後空格的移除:trim
  10. 起始字元串/終止字元串是否與給定字元串相同:startsWith、endWith
  11. 是否包含字元串:contains
  12. 基本類型轉換為字元串類型:valueOf

7、String拼接的場景中是否一定要使用StringBuilder或者StringBuffer?

不是!

1. String的可讀性更好;StringBuilder的可讀性差。

1. JDK 8開始,

“a”+“b”+“c”

會預設采用

StringBuilder

實作(反編譯後可以看見)

1. JDK 9中,提供了更加統一的字元串操作優化,提供了

StringConcatFactory

作為同一入口。

8、String底層實作采用char導緻的問題?

  1. char是兩個

    bytes

    大小,拉丁語系語言的字元不需要這麼寬的char。
  2. char的無差別實作,會導緻浪費。
  3. Java9中采用byte[]數組來實作。

常量池

9、Java為了避免在系統中産生大量的String對象,引入了字元串常量池。

  1. 建立字元串時,會到常量池中檢查是否有相同的字元串對象。
  2. 如果有,就直接傳回其引用。
  3. 如果沒有,會建立字元串對象,将其放入常量池,并且傳回引用。

10、字元串常量池有什麼用?

Java為了避免在系統中産生大量的String對象

11、所有的String都是字元串常量池?

錯誤!

12、直接指派的String對象才會放入字元串常量池:

String str = “bitch”;

13、new方式建立的String對象不妨放入常量池:

String str = new String("Hello");

  1. 關鍵字new

    建立String對象,不會去檢查常量池
  2. new建立String會直接在

    堆區

    或者

    棧區

    建立一個新的對象,也不會放入池中。

14、

String str = new String("AB");

是否還會涉及到常量池?如何驗證?

15、

String str = new String("AB");

會建立幾個對象?

兩個對象!

1. “AB”會先建立String對象,内容為”AB”。再用該string去建立str。

1. 建議使用

String str = "AB"

, 隻會建立一個對象。

16、String的直接指派得到的對象和new建立的對象是否相同?

String s1 = "hello world!";
        String s2 = "hello world!";
        String s3 = new String(s1);
        String s4 = new String("hello world!");

        System.out.println("s1 == s2 : " + (s1 == s2));
        System.out.println("s1 == s3 : " + (s1 == s3));
        System.out.println("s3 == s4 : " + (s3 == s4));
           
  1. s1和s2是同一個對象(常量池複用)。
  2. s3,s4都是建立的額外的對象。

intern

17、JVM常量池位置的變更

  1. JDK1.6,JVM運作時資料區的

    方法區

    中,有一個常量池。
  2. JDK1.6以後,常量池位于

    堆空間

  3. 常量池的位置會影響intern的效果。

18、intern在JDK1.6和JDK1.7的差別

  1. JDK1.6: intern()方法會把首次遇到的字元串執行個體複制到永久代(可以就當是方法區)中,傳回的也是永久代中這個字元串執行個體的引用。
  2. JDK1.7: intern()實作不會複制執行個體,隻是在常量池中記錄首次出現的執行個體的引用。
//JDK1.6
String s3 = new String("1") + new String("1");
// 1. 将s3(“11”)的複制品,放到永久帶中。
s3.intern();
// 2. s4 = s3的複制品
String s4 = "11";
// 3. 一定不相等,結果為false
System.out.println(s3 == s4);
           
//JDK1.7
String s3 = new String("1") + new String("1");
// 1. 将s3(“11”)的引用記錄在常量池中。
s3.intern();
// 2. s4 = s3的引用
String s4 = "11";
// 3. 一定相等,結果為true
System.out.println(s3 == s4);
           

19、String的intern方法的執行個體

1-如下情況有什麼結果?(JDK7)
// 1. 生成常量池中的Hello、World!生成位于堆空間中“HelloWorld!”的對象
String str1 = new String("Hello")+ new String("World!");
// 2、 str1.intern(),在常量池中記錄首次出現的執行個體的引用。也就是str1的引用,是以str1.intern() = str1,和str1相比,肯定一樣。
System.out.println(str1.intern() == str1);
// 3、“HelloWorld!”會去常量池找,找到了str1的引用
System.out.println(str1 == "HelloWorld!");
           
true
true
           
2-如下情況有什麼結果?(JDK7)
// 1. 現在常量池中生成"HelloWorld!"
String str2 = "HelloWorld!";
// 2. 生成常量池中的Hello、World!生成位于堆空間中“HelloWorld!”的對象,也就是str1
String str1 = new String("Hello")+ new String("World!");
// 3、 str1.intern(),在常量池中記錄首次出現的執行個體的引用。也就是"HelloWorld!"的引用,是以str1.intern()=常量"HelloWorld!" 不等于 str1(堆空間對象)
System.out.println(str1.intern() == str1);
// 4、“HelloWorld!”會去常量池找,找到了,傳回常量池的引用。是以和str1一定不相等
System.out.println(str1 == "HelloWorld!");
           
false
false
           
3-如下情況有什麼結果?(JDK7)
// 1、常量"1"和堆空間對象“1”
        String s = new String("1");
        // 2、在常量池中記錄首次出現的執行個體的引用。也就是常量“1”的引用
        s.intern();
        // 3、從常量池中擷取到常量“1”的引用。
        String s2 = "1";
        // 4、s2=常量“1”, s=對象“1”,一定不相等
        System.out.println(s == s2);
           
4-如下情況有什麼結果?(JDK7)
// 1、生成常量“1”,s3=堆空間對象“11”
        String s3 = new String("1") + new String("1");
        // 2、在常量池中記錄首次出現的執行個體的引用。也就是s3的引用。
        s3.intern();
        // 3、去常量池找“11”,擷取到s3的引用
        String s4 = "11";
        // 4、s4和s3肯定相同 = true
        System.out.println(s3 == s4);
           

AbstractStringBuilder

1、AbstractStringBuilder是什麼(2)

  1. StringBuider

    StringBuffer

    都是繼承自

    AbstractStringBuilder

  2. 内部是一個char[]數組

    沒有final

    修飾符。

2、AbstractStringBuilder的API分類(7)

  1. 擴容:ensureCapacity、newCapacity、hugeCapacity—

    擴容方式:以前大小 * 2 + 2

  2. 追加:append(容量不夠就擴容,容量夠就追加到

    value數組

    的最後)
  3. 插入字元串:insert
  4. 删除字元串:delete(删除[start, end]之間的字元,通過複制的方式實作)
  5. 提取子串:substring(new一個新String)
  6. 字元串的替換:replace
  7. 字元串某位置的字元:charAt

3、StringBuffer和StringBuilder的擴容問題

  1. 預設容量是

    16

  2. 如果知道需要的容量需要

    手動設定

  3. 頻繁擴容會導緻嚴重的性能損耗,會涉及到建立新數組和

    arraycopy

    的資料複制,會産生的性能問題。

StringBuffer

4、StringBuffer的特點(3)

  1. 繼承自

    AbstractStringBuilder

  2. 構造

    預設是建立

    容量為16

    AbstractStringBuilder

  3. 線程安全:

    StringBuffer

    的所有方法,都是直接使用父類的方法,并都是用

    synchronized

    進行加鎖保護。
  4. 性能比StringBuilder低。

5、StringBuffer的适用場景?

  1. 線程安全

    :适用于

    多線程

  2. Http參數拼接、xml解析

StringBuilder

6、StringBuilder的特點(4)

  1. 繼承自

    AbstractStringBuilder

  2. Java 1.5中新增
  3. 初始容量16.
  4. 非線程安全

    StringBuilder

    的所有方法,都是直接使用父類的方法,但是沒有使用

    synchronized

    進行加鎖保護。
  5. 性能最高,在單線程中推薦使用。

7、StringBuilder的适用場景?

  1. 非線程安全

    :适用于

    單線程

  2. SQL語句拼接
  3. JSON封裝
  4. XML解析

字元串緩存

1、字元串重複的開銷問題

  1. 經過分析,對象中平均25%都是字元串
  2. 字元串的50%都是重複的字元串。
  3. 如果進行優化,能有效降低記憶體消耗、對象建立開銷。

2、Java 6開始提供的intern()

  1. 一種顯式的排重機制。
  2. string.intern();

    能提示JVM把相應的字元串緩存起來,以備重複使用。
  3. 調用該方法時,如果常量池中有String和該String相等,則直接傳回常量池中的字元串。(String的equals進行比較)
  4. 如果常量池中沒有該字元串,則将String對象,添加到常量池中,并且傳回引用。

3、Java 6中intern的嚴重缺陷

  1. Java 6中并不推薦使用

    intern

  2. 将緩存的字元串存儲到了

    PermGen

    裡,也就是臭名昭著的

    永久代

  3. FullGC以外的垃圾回收都不會涉及到

    永久代

  4. 使用不當,會導緻

    OOM

    問題

4、Java 6以後的字元串緩存的優化

  1. 後續采用了

    來替代

    永久代

    來存儲

    字元串

  2. Java 8中采用

    MetaSpace(中繼資料區)

5、字元串緩存大小?

  1. 随着發展,已經從最初的

    1009

    提升到了

    60013

  2. -XX:+PrintStringTableStatistics

    可以檢視

    具體數值

  3. -XX:StringTableSize=XXX

    能手動修改,

    不建議修改

6、intern()的副作用

  1. 需要開發者顯式調用,使用不友善
  2. 很難保證效率,因為開發者難以清楚

    字元串的重複率

    ,最終可能導緻代碼的污染。

字元串排重

7、Oracle JDK 8u20後,推出了字元串排重的新特性

  1. G1 GC

    下的字元串排重
  2. 做法:将相同資料的字元串指向同一份資料
  3. 這種方法是JVM底層的改變,并不需要Java類庫做什麼改變。

8、JDK 8 的字元串排重的功能預設是關閉的

1-需要指定使用G1 GC
-XX:+UseG1GC
           
2-開啟字元串排重功能
-XX:+UseStringDeduplication
           

Intrinsic機制

9、JVM内部的Intrinsic機制是幹什麼的?

  1. 是一種利用native方法,hard-coded(寫死)的邏輯,
  2. 一種特殊的内聯(intrinsic-内在的)。
  3. 很多優化還是需要直接使用特定的CPU指令。

10、字元串如何利用Intrinsic機制優化的?

  1. 字元串的特殊操作運作的都是特殊優化的

    本地代碼

  2. 而不是去運作

    Java代碼

    生成的

    位元組碼

Java9 Compact String

1、Java9中StringBuffer和StringBuilder底層的char[]數組都變更為byte[]數組

2、Java9中的字元串引入了Compact Strings的設計

  1. String不再使用

    char數組

  2. String采用

    byte數組

    實作,并且加上辨別編碼

    coder

  3. 将String相關的操作類都進行了修改。
  4. String相關的

    intrinsic

    類都進行了重寫

知識擴充

1、String是典型的immutable類,final修飾的類是否就是immutable的類?

不是

2、final的作用?(類、變量、方法)

3、如何去實作一個immutable類?

4、Java中String的緩存機制。

5、getBytes()和new String()采用的什麼編碼方式?

  1. 會先從JVM參數中找有沒有指定的file.encoding參數
  2. 沒有找到,會采用作業系統環境的編碼方式。
  3. 建議:getBytes/String相關業務需要指定

    編碼方式

    ,否則因為其不确定性,可能會導緻問題。

6、JDK中String的hash值為什麼沒有采用final修飾,也沒有考慮同步問題?

  1. String的hash值沒有采用final修飾,其計算方式是在第一次調用

    hashcode()

    時生成。
  2. hashcode()方法沒有加鎖,沒有采用

    valatile

    修飾。在多線程中可能會出現

    hash值

    多次計算。
  3. 雖然運算結果是一緻的(同一個對象調用hashcode方法,結果肯定是一緻的),為什麼不去優化這種會多次計算的情況。
  4. 這種優化會導緻在通用場景變成

    持續的成本

    ,volatile有明顯開銷,但是沖突并不多見。是以不需要這種優化。
public int hashCode() {
    int h = hash;
    // 1. h == 0, 決定了在單線程中隻會計算一次。
    if (h ==  && value.length > ) {
        char val[] = value;
        for (int i = ; i < value.length; i++) {
            h =  * h + val[i];
        }
        hash = h;
    }
    return h;
}
           

7、為什麼StringBuilder、StringBuffer要給定初始值?

  1. 如果采用預設容量16,在字元串很長的情況下,會導緻多次擴容。
  2. 擴容時的建立新數組,arrayCopy的複制都會影響性能。
  3. 建議在使用時,預估會用到的字元串長度,合理的設定容量。

8、String采用的不可變模式的優點?

  1. 不可變模式是一種優質的設計模式。
  2. 能提高多線程程式性能。
  3. 能降低多線程程式的複雜度。

9、String常量池的優化

  1. 當兩個String對象擁有相同内容是,隻會引用常量池中同一個内容。

10、如何列印對象的位址?

1-直接列印對象: 會顯示其位址(在@後面),十六進制。如果對象重寫了toString()就不會列印出記憶體位址。
2-對象的toString(): 會顯示其位址(在@後面),十六進制。如果對象重寫了toString()就不會列印出記憶體位址。
2-對象的hashCode(): 等于記憶體位址(十進制)。如果對象重寫了hashCode(),會導緻數值和記憶體位址不相關的了。

11、如何列印出String對象的位址?

1-使用

System.identityHashCode(s1)

可以計算出任何對象的hashCode(根據記憶體位址得到),就算重寫過

hashCode()

也不會影響
String s1 = "Hello World for Feather!";
String s2 = "Hello World for Feather!";
String s3 = new String(s2);
String s4 = new String("Hello World for Feather!");

System.out.println(System.identityHashCode(s1));
System.out.println(System.identityHashCode(s2));
System.out.println(System.identityHashCode(s3));
System.out.println(System.identityHashCode(s4));
\\輸出結果
s1 
s2 
s3 
s4 
           
2-用==來比較是否相等,但是無法比較。

12、如何通過string調用Object的toString?

不可以

編譯和反編譯

1、Java代碼的編譯和反編譯

  1. javac: 編譯
  2. javap: 反編譯
javac Main.java
javap -v Main.class
           

知識儲備

1、JEP 193: Variable Handles 是什麼?

Variable Handles的API主要是用來取代

java.util.concurrent.atomic包

以及

sun.misc.Unsafe類

的功能。

參考資料

  1. Java技術——你真的了解String類的intern()方法嗎