轉載請注明連結:https://blog.csdn.net/feather_wch/article/details/82389184
本文包括如下内容:
- String的特點
- StringBuider的特點
- StringBuffer的特點
- 什麼是字元串緩存的intern機制
- 什麼是字元串排重
- 什麼是intrinsic機制
- Java 9中String的改進
String、SrtingBuilder、StringBuffer詳解
版本号:2018/9/5-1(16:16)
- String、SrtingBuilder、StringBuffer詳解
- 問題彙總
- String
- 常量池
- intern
- AbstractStringBuilder
- StringBuffer
- StringBuilder
- 字元串緩存
- 字元串排重
- Intrinsic機制
- Java9 Compact String
- 知識擴充
- 編譯和反編譯
- 知識儲備
- 參考資料
問題彙總
- 【☆】String包含哪些方面的知識?
- String、StringBuilder、StringBuffer的特點和差別。
- 字元串緩存的intern機制
- 字元串排重(JVM)
- Intrinsic機制
- JAVA9的Compat Strings
- String、StringBuffer、StringBuilder的差別
- String的特點
- String的immutabale特性有哪些優點呢?
- String的内部原理
- String比較的equals和==的差別
- String API的分類(12)
- String拼接的場景中是否一定要使用StringBuilder或者StringBuffer?
- String底層實作采用char導緻的問題?
- AbstractStringBuilder是什麼(2)
- AbstractStringBuilder的API分類(7)
- StringBuffer和StringBuilder的擴容問題(預設容量和性能損耗)
- StringBuffer的特點(3)
- StringBuffer的适用場景?
- StringBuilder的特點(4)
- StringBuilder的适用場景?
- 字元串重複的開銷問題
- Java 6開始提供的intern()的作用
- Java 6中intern的嚴重缺陷
- Java 6以後的字元串緩存的優化
- 字元串緩存大小?如何修改?
- Java6以後intern()的副作用
- Oracle JDK 8u20後,推出了字元串排重的新特性
- JDK 8 的字元串排重的功能如何開啟?(GC1)
- JVM内部的Intrinsic機制是幹什麼的?
- 字元串如何利用Intrinsic機制優化的?
- Java9中StringBuffer和StringBuilder底層的char[]數組都變更為byte[]數組
- Java9中的字元串引入了Compact Strings進行了哪些方面的修改?
- String是典型的immutable類,final修飾的類是否就是immutable的類?
- final的作用?(類、變量、方法)
- 如何去實作一個immutable類?
- 【☆】Java中對String的緩存機制?
intern()、JDK8JVM層的字元串排重
- getBytes()和new String()采用的什麼編碼方式?
- JDK中String的hash值為什麼沒有采用final修飾,也沒有考慮hashcode()在多線程中會重複計算的問題?
- Java為了避免在系統中産生大量的String對象,引入了字元串常量池。
- 字元串常量池有什麼用?
- 所有的String都是字元串常量池?
- 【☆】建立字元串對象的兩種方式?
- 直接指派:
String str = “bitch”;
- new方式建立
- 直接指派:
- new方式建立的String對象是否會采用字元串常量池?
- 為什麼StringBuilder、StringBuffer要給定初始值?
- String采用的不可變模式的優點?
- String常量池的優化機制?
- String str = new String(“AB”)是否還會涉及到常量池?如何驗證?
- String str = new String(“AB”)會建立幾個對象?
- 【☆】String str = “AB”會建立幾個對象?
隻會建立一個對象。
- 如何列印對象的位址?
- 如何列印出String對象的位址?
- 如何通過string調用Object的toString
- intern在JDK1.6和JDK1.7的差別?
String
1、String、StringBuffer、StringBuilder的差別
String | 特點 | 線程安全 | 性能 |
---|---|---|---|
String | 提供字元串相關功能。内部是final char[]數組。是典型的 類(final的class、final的字段)。 | 安全 | 性能低,内不會産生新的String對象。 |
StringBuffer | 用于解決字元串拼接産生的中間對象的問題。繼承自 内部是char[]數組 | 安全 | 性能稍低,采用synchronized進行加鎖。 |
StringBuilder | 繼承自 内部是char[]數組 | 線程不安全 | 性能高 |
2、String的特點(4)
- String是典型的
類(不可變的):
immutable
修改String不會在原有的記憶體位址修改,而是重新指向一個新對象
- String用final修飾,不可以
:String本質是
繼承
,是以
final的char[]數組
數組的記憶體位址不會被修改,而且
char[]
沒有對外暴露修改
String
的方法。
char[]數組
- String是線程安全的:因為其是
類,實作
immutable
。
字元串常量池
- 頻繁的
不建議使用
增删操作
String
- 操作不當會導緻大量臨時String對象。
3、String的immutabale特性有哪些優點呢?
- 性能安全
- 拷貝:不需要額外複制資料。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
4、String的内部原理
内部是一個
String
:名稱為
final修飾 char[]數組
value
本質上就是對
String的構造
賦初值的過程。
value數組
的其他API本質都是對
String
操作的過程。如:
value數組
是通過
substring
完成的,最終會
數組的複制
建立的
傳回
,不會影響到原有的數組。
String
5、String比較的equals和==的差別
- astr.equals(bstr) 比較對象是否相等
- == 僅僅是比較首位址
6、String API的分類(12)
- 構造方法
- 字元串長度:length
- 字元串某一位置:charAt
- 提取子串:substring
- 字元串比較:compareTo、compareToIgnore、equals、equalsIgnoreCase
- 字元串的連接配接:concat
- 字元串的查找:indexOf、lastIndexOf
- 字元串的替換:replace
- 前後空格的移除:trim
- 起始字元串/終止字元串是否與給定字元串相同:startsWith、endWith
- 是否包含字元串:contains
- 基本類型轉換為字元串類型:valueOf
7、String拼接的場景中是否一定要使用StringBuilder或者StringBuffer?
不是!
1. String的可讀性更好;StringBuilder的可讀性差。
1. JDK 8開始,
會預設采用
“a”+“b”+“c”
StringBuilder
實作(反編譯後可以看見)
1. JDK 9中,提供了更加統一的字元串操作優化,提供了
作為同一入口。
StringConcatFactory
8、String底層實作采用char導緻的問題?
- char是兩個
大小,拉丁語系語言的字元不需要這麼寬的char。
bytes
- char的無差別實作,會導緻浪費。
- Java9中采用byte[]數組來實作。
常量池
9、Java為了避免在系統中産生大量的String對象,引入了字元串常量池。
- 建立字元串時,會到常量池中檢查是否有相同的字元串對象。
- 如果有,就直接傳回其引用。
- 如果沒有,會建立字元串對象,将其放入常量池,并且傳回引用。
10、字元串常量池有什麼用?
Java為了避免在系統中産生大量的String對象
11、所有的String都是字元串常量池?
錯誤!
12、直接指派的String對象才會放入字元串常量池:
String str = “bitch”;
13、new方式建立的String對象不妨放入常量池:
String str = new String("Hello");
建立String對象,不會去檢查常量池
關鍵字new
- 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));
- s1和s2是同一個對象(常量池複用)。
- s3,s4都是建立的額外的對象。
intern
17、JVM常量池位置的變更
- JDK1.6,JVM運作時資料區的
中,有一個常量池。
方法區
- JDK1.6以後,常量池位于
堆空間
- 常量池的位置會影響intern的效果。
18、intern在JDK1.6和JDK1.7的差別
- JDK1.6: intern()方法會把首次遇到的字元串執行個體複制到永久代(可以就當是方法區)中,傳回的也是永久代中這個字元串執行個體的引用。
- 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)
和
StringBuider
都是繼承自
StringBuffer
AbstractStringBuilder
,
内部是一個char[]數組
修飾符。
沒有final
2、AbstractStringBuilder的API分類(7)
- 擴容:ensureCapacity、newCapacity、hugeCapacity—
擴容方式:以前大小 * 2 + 2
- 追加:append(容量不夠就擴容,容量夠就追加到
的最後)
value數組
- 插入字元串:insert
- 删除字元串:delete(删除[start, end]之間的字元,通過複制的方式實作)
- 提取子串:substring(new一個新String)
- 字元串的替換:replace
- 字元串某位置的字元:charAt
3、StringBuffer和StringBuilder的擴容問題
- 預設容量是
16
- 如果知道需要的容量需要
手動設定
- 頻繁擴容會導緻嚴重的性能損耗,會涉及到建立新數組和
的資料複制,會産生的性能問題。
arraycopy
StringBuffer
4、StringBuffer的特點(3)
- 繼承自
AbstractStringBuilder
預設是建立
構造
的
容量為16
AbstractStringBuilder
- 線程安全:
的所有方法,都是直接使用父類的方法,并都是用
StringBuffer
進行加鎖保護。
synchronized
- 性能比StringBuilder低。
5、StringBuffer的适用場景?
:适用于
線程安全
多線程
- Http參數拼接、xml解析
StringBuilder
6、StringBuilder的特點(4)
- 繼承自
AbstractStringBuilder
- Java 1.5中新增
- 初始容量16.
:
非線程安全
的所有方法,都是直接使用父類的方法,但是沒有使用
StringBuilder
進行加鎖保護。
synchronized
- 性能最高,在單線程中推薦使用。
7、StringBuilder的适用場景?
:适用于
非線程安全
單線程
- SQL語句拼接
- JSON封裝
- XML解析
字元串緩存
1、字元串重複的開銷問題
- 經過分析,對象中平均25%都是字元串
- 字元串的50%都是重複的字元串。
- 如果進行優化,能有效降低記憶體消耗、對象建立開銷。
2、Java 6開始提供的intern()
- 一種顯式的排重機制。
能提示JVM把相應的字元串緩存起來,以備重複使用。
string.intern();
- 調用該方法時,如果常量池中有String和該String相等,則直接傳回常量池中的字元串。(String的equals進行比較)
- 如果常量池中沒有該字元串,則将String對象,添加到常量池中,并且傳回引用。
3、Java 6中intern的嚴重缺陷
- Java 6中并不推薦使用
intern
- 将緩存的字元串存儲到了
裡,也就是臭名昭著的
PermGen
永久代
- FullGC以外的垃圾回收都不會涉及到
永久代
- 使用不當,會導緻
問題
OOM
4、Java 6以後的字元串緩存的優化
- 後續采用了
來替代
堆
來存儲
永久代
字元串
- Java 8中采用
MetaSpace(中繼資料區)
5、字元串緩存大小?
- 随着發展,已經從最初的
提升到了
1009
60013
可以檢視
-XX:+PrintStringTableStatistics
具體數值
能手動修改,
-XX:StringTableSize=XXX
不建議修改
6、intern()的副作用
- 需要開發者顯式調用,使用不友善
- 很難保證效率,因為開發者難以清楚
,最終可能導緻代碼的污染。
字元串的重複率
字元串排重
7、Oracle JDK 8u20後,推出了字元串排重的新特性
下的字元串排重
G1 GC
- 做法:将相同資料的字元串指向同一份資料
- 這種方法是JVM底層的改變,并不需要Java類庫做什麼改變。
8、JDK 8 的字元串排重的功能預設是關閉的
1-需要指定使用G1 GC
-XX:+UseG1GC
2-開啟字元串排重功能
-XX:+UseStringDeduplication
Intrinsic機制
9、JVM内部的Intrinsic機制是幹什麼的?
- 是一種利用native方法,hard-coded(寫死)的邏輯,
- 一種特殊的内聯(intrinsic-内在的)。
- 很多優化還是需要直接使用特定的CPU指令。
10、字元串如何利用Intrinsic機制優化的?
- 字元串的特殊操作運作的都是特殊優化的
本地代碼
- 而不是去運作
生成的
Java代碼
位元組碼
Java9 Compact String
1、Java9中StringBuffer和StringBuilder底層的char[]數組都變更為byte[]數組
2、Java9中的字元串引入了Compact Strings的設計
- String不再使用
char數組
- String采用
實作,并且加上辨別編碼
byte數組
coder
- 将String相關的操作類都進行了修改。
- String相關的
類都進行了重寫
intrinsic
知識擴充
1、String是典型的immutable類,final修飾的類是否就是immutable的類?
不是
2、final的作用?(類、變量、方法)
3、如何去實作一個immutable類?
4、Java中String的緩存機制。
5、getBytes()和new String()采用的什麼編碼方式?
- 會先從JVM參數中找有沒有指定的file.encoding參數
- 沒有找到,會采用作業系統環境的編碼方式。
- 建議:getBytes/String相關業務需要指定
,否則因為其不确定性,可能會導緻問題。
編碼方式
6、JDK中String的hash值為什麼沒有采用final修飾,也沒有考慮同步問題?
- String的hash值沒有采用final修飾,其計算方式是在第一次調用
時生成。
hashcode()
- hashcode()方法沒有加鎖,沒有采用
修飾。在多線程中可能會出現
valatile
多次計算。
hash值
- 雖然運算結果是一緻的(同一個對象調用hashcode方法,結果肯定是一緻的),為什麼不去優化這種會多次計算的情況。
- 這種優化會導緻在通用場景變成
,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要給定初始值?
- 如果采用預設容量16,在字元串很長的情況下,會導緻多次擴容。
- 擴容時的建立新數組,arrayCopy的複制都會影響性能。
- 建議在使用時,預估會用到的字元串長度,合理的設定容量。
8、String采用的不可變模式的優點?
- 不可變模式是一種優質的設計模式。
- 能提高多線程程式性能。
- 能降低多線程程式的複雜度。
9、String常量池的優化
- 當兩個String對象擁有相同内容是,隻會引用常量池中同一個内容。
10、如何列印對象的位址?
1-直接列印對象: 會顯示其位址(在@後面),十六進制。如果對象重寫了toString()就不會列印出記憶體位址。
2-對象的toString(): 會顯示其位址(在@後面),十六進制。如果對象重寫了toString()就不會列印出記憶體位址。
2-對象的hashCode(): 等于記憶體位址(十進制)。如果對象重寫了hashCode(),會導緻數值和記憶體位址不相關的了。
11、如何列印出String對象的位址?
1-使用可以計算出任何對象的hashCode(根據記憶體位址得到),就算重寫過
System.identityHashCode(s1)
也不會影響
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代碼的編譯和反編譯
- javac: 編譯
- javap: 反編譯
javac Main.java
javap -v Main.class
知識儲備
1、JEP 193: Variable Handles 是什麼?
Variable Handles的API主要是用來取代以及
java.util.concurrent.atomic包
的功能。
sun.misc.Unsafe類
參考資料
- Java技術——你真的了解String類的intern()方法嗎