StringTable
String的基本特性
●?String:字符串,使用一對""引起來表示米间。
●?String聲明為final的, 不可被繼承
●?String實(shí)現(xiàn)了Serializable接口:表示字符串是支持序列化的。實(shí)現(xiàn)了Comparable接口:表示String可以比較大小
●?String在jdk8及以前內(nèi)部定義了final char value[]用于存儲字符串?dāng)?shù)據(jù)赂苗。jdk9時改為byte []。
●?String:代表不可變的字符序列贮尉。簡稱:不可變性拌滋。
???當(dāng)對字符串重新賦值時,需要重寫指定內(nèi)存區(qū)域賦值猜谚,不能使用原有的value進(jìn)行賦值败砂。
???當(dāng)對現(xiàn)有的字符串進(jìn)行連接操作時,也需要重新指定內(nèi)存區(qū)域賦值魏铅,不能使用原有的value進(jìn)行賦值昌犹。
???當(dāng)調(diào)用String的replace ()方法修改指定字符或字符串時,也需要重新指定內(nèi)存區(qū)域賦值览芳,不能使用原有的value進(jìn)行賦值斜姥。
●?通過字面量的方式(區(qū)別于new)給一個字符串賦值,此時的字符串值聲明在字符串常量池中沧竟。
●?字符串常量池中是不會存儲相同內(nèi)容的字符串的铸敏。
??string的String Pool 是一個固定大小的Hashtable,默認(rèn)值大小長度是1009屯仗。如果放進(jìn)String Pool 的String非常多搞坝,就會造成Hash沖突嚴(yán)重,從而導(dǎo)致鏈表會很長魁袜,而鏈表長了后直接會造成的影響就是當(dāng)調(diào)用String.intern時性能會大幅下降桩撮。
??使用-Xx: StringTableSize可設(shè)置StringTable的長度
??在jdk6中StringTable是固定的,就是1009的長度峰弹,所以如果常量池中的字符串過多就會導(dǎo)致效率下降很快店量。StringTableSize設(shè)置沒有要求
??在jdk7中,StringTable的長度默認(rèn)值是60013
??在jdk8開始鞠呈,StringTable的長度1009是可設(shè)置的最小值融师。
String的內(nèi)存分配
●?在Java語言中有8種基本數(shù)據(jù)類型和一種比較特殊的類型String。這些類型為了使它們在運(yùn)行過程中速度更快蚁吝、更節(jié)省內(nèi)存旱爆,都提供了一種常量池的概念舀射。
●?常量池就類似一個Java系統(tǒng)級別提供的緩存。8種基本數(shù)據(jù)類型的常量池都是系統(tǒng)協(xié)調(diào)的怀伦,String類型的常量池比較特殊脆烟。它的主要使用方法有兩種。
???直接使用雙引號聲明出來的String對象會直接存儲在常量池中房待。
?????比如:String info = "atguigu. com";
???如果不是用雙引號聲明的String對象邢羔,可以使用String提供的intern()方法。
●?Java 6及以前桑孩,字符串常量池存放在永久代拜鹤。
●?Java 7中Oracle的工程師對字符串池的邏輯做了很大的改變,即將字符串常量池的位置調(diào)整到Java堆內(nèi)流椒。
???所有的字符串都保存在堆(Heap)中敏簿,和其他普通對象一樣,這樣可以讓你在進(jìn)行調(diào)優(yōu)應(yīng)用時僅需要調(diào)整堆大小就可以了镣隶。
???字符串常量池概念原本使用得比較多极谊,但是這個改動使得我們有足夠的理由讓我們重新考慮在Java 7中使用String. intern()。
●?Java8元空間安岂,字符串常量在堆
String的基本操作
案例1:
常量池中對象的個數(shù):
Java語言規(guī)范里要求完全相同的字符串字面量轻猖,應(yīng)該包含同樣的Unicode字符序列(包含同一份碼點(diǎn)序列的常量),并且必須是指向同一個String類實(shí)例域那。
案例2:
字符串拼接操作
1.常量與常量的拼接結(jié)果在常量池咙边,原理是編譯期優(yōu)化
2.常量池中不會存在相同內(nèi)容的常量。
3.只要其中有一個是變量次员,結(jié)果就在堆中败许。變量拼接的原理是StringBuilder
4.如果拼接的結(jié)果調(diào)用intern()方法,則主動將常量池中還沒有的字符串對象放入池中淑蔚,并返回此對象地址市殷。
String 字符串常量
StringBuffer 字符串變量(線程安全)
StringBuilder 字符串變量(非線程安全)
intern()的使用
如果不是用雙引號聲明的String對象,可以使用String提供的intern方法:intern方法會從字符串常量池中查詢當(dāng)前字符串是否存在刹衫,若不存在就會將當(dāng)前字符串放入常量池中醋寝。
●?比如:
String myInfo = new String("I love atguigu").intern();
也就是說,如果在任意字符串上調(diào)用String.intern方法带迟,那么其返回結(jié)果所指向的那個類實(shí)例少必須和直接以常量形式出現(xiàn)的字符串實(shí)例完全相同音羞。因此,下 列表達(dá)式的值必定是true:
("a" + "b" + "c").intern() == "abc"
通俗點(diǎn)講仓犬,Interned String 就是確保字符串在內(nèi)存里只有一份拷貝嗅绰,這樣可以節(jié)約內(nèi)存空間,加快字符串操作任務(wù)的執(zhí)行速度。注意窘面,這個值會被存放在字符串內(nèi)部池(String Intern Pool)翠语。
面試題
題目1:new String("ab")會創(chuàng)建幾個對象?
題目2:new String("a") + new String("b")呢民镜?
答案1:兩個啡专,一個是new關(guān)鍵字在堆內(nèi)存中創(chuàng)建String,另外一個是字符串常量池中的對象制圈,字節(jié)碼指令:ldc
答案2:
?對象1:new StringBuilder()
?對象2:new String("a")
?對象3:常量池中的"a"
?對象4:new String("b")
?對象5:常量池中的"b"
?深入剖析:StringBuilder的toString()方法:
??對象6:new String("ab")
??強(qiáng)調(diào)一下,toString()的調(diào)用畔况,在字符串常量池中鲸鹦,沒有生成"ab"
jdk6 VS jdk7/8
public class stringIntern1 {
public static void main(String[] args) {
string S = new string("1");
s.intern(); //調(diào)用此方法之前,字符串常量池中已經(jīng)存在了“1”
string s2 ="1";
System.out.println(s == s2); // jdk6:false jdk7/8:false s是堆空間中的地址跷跪,s2是常量池中的地址
string s3 = new String("1") + new string("1"); // s3變量記錄的地址為new String("11");
// 執(zhí)行完上一行代碼后:字符串常量池中馋嗜,是否存在“11”呢? 答案:不存在吵瞻!
s3.intern( );
// 在字符串常量池中生成對象“11”葛菇。
/*
如何理解:
jdk6:創(chuàng)建一個新的對象“11”,也就有新的地址
jdk7:此時常量池中并沒有創(chuàng)建“11”橡羞,而是創(chuàng)建了一個指向堆空間中new String("11")的地址
*/
string s4 = "11"; // s4變量記錄的地址:上一行代碼代碼執(zhí)行時眯停,在常量池中生成的“11”的地址
System.out.println(s3 == s4); // jdk6:false jdk7/8:true
}
}
拓展
public class stringIntern1 {
public static void main(String[] args) {
string s3 = new String("1") + new string("1"); // s3變量記錄的地址為new String("11");
// 執(zhí)行完上一行代碼后:字符串常量池中,是否存在“11”呢卿泽? 答案:不存在莺债!
string s4 = "11"; // 在字符串常量池中生成對象“11”。
string s5 = s3.intern( ); // 發(fā)現(xiàn)常量池中有對象“11”签夭,s5指向常量池中的對象“11”
System.out.println(s3 == s4); // jdk7/8:false
System.out.println(s5 == s4); // jdk7/8:true
}
}
總結(jié)String的intern()的使用:
●?jdk1.6中齐邦,將這個字符串對象嘗試放入串池。
???如果串池中有第租,則并不會放入措拇。返回已有的串池中的對象的地址
???如果沒有,會把此對象復(fù)制一份慎宾,放入串池丐吓,并返回串池中的對象地址
●?Jdk1.7起,將這個字符串對象嘗試放入串池璧诵。
???如果串池中有汰蜘,則并不會放入。返回已有的串池中的對象的地址
???如果沒有之宿,則會把對象的引用地址復(fù)制一份族操,放入串池,并返回串池中的引用地址
練習(xí)1:
public class stringExer1 {
public static void main(String[] args) {
String s = new String("a") + new string("b"); //new String( "ab")
// 在上一行代碼執(zhí)行完以后,字符串常量池中并沒有“ab”
String s2 = s.intern();
// jdk6中:在串池中創(chuàng)建一個字 符串“ab”
// jdk8中:串池中沒有創(chuàng)建字符串"ab",而是創(chuàng)建一一個引用色难, 指向new String("ab")泼舱,將此引用返回
System.out.println(s2 == " ab"); // jdk6:true jdk8:true
System.out.println(s == "ab"); // jdk6:false jdk8:true
}
}
練習(xí)2:
public class stringExer2 {
public static void main(String[] args) {
// 會在常量池中生成“ab”
// String s1 = new String("ab"); // jdk8:false
// 會在常量池中生成“ab”
String s1 = new string( original: "a") + new string( original: "b"); // jdk8:true
s1. intern();
String s2 = "ab";
System.out.println(s1 == s2);
}
}
G1中的String去重操作
●?背景:對許多Java應(yīng)用(有大的也有小的)做的測試得出以下結(jié)果:
???堆存活數(shù)據(jù)集合里面string對象占了25%
???堆存活數(shù)據(jù)集合里面重復(fù)的String對象有13.5%
???String對象的平均長度是45
●?許多大規(guī)模的Java應(yīng)用的瓶頸在于內(nèi)存,測試表明枷莉,在這些類型的應(yīng)用里面娇昙,Java堆中存活的數(shù)據(jù)集合差不多25%是string對象。更進(jìn)一步笤妙,這里面差不多一半String對象是重復(fù)的冒掌,重復(fù)的意思是說:string1.equals(string2)=true。堆上存在重復(fù)的String對象必然是一種內(nèi)存的浪費(fèi)蹲盘。這個項(xiàng)目將在G1垃圾收集器中實(shí)現(xiàn)自動持續(xù)對重復(fù)的String對象進(jìn)行去重股毫,這樣就能避免浪費(fèi)內(nèi)存。
●?實(shí)現(xiàn)
???當(dāng)垃圾收集器工作的時候召衔,會訪問堆上存活的對象铃诬。對每一個訪問的對象都會檢查是否是候選的要去重的String對象。
???如果是苍凛,把這個對象的一個引用插入到隊(duì)列中等待后續(xù)的處理趣席。一個去重的線程在后臺運(yùn)行,處理這個隊(duì)列醇蝴。處理隊(duì)列的一個元素意味著從隊(duì)列刪除這個元素宣肚,然后嘗試去重它引用的String對象。
???使用一個hashtable來記錄所有的被String對象使用的不重復(fù)的char數(shù)組哑蔫。當(dāng)去重的時候钉寝,會查這個hashtable,來看堆上是否已經(jīng)存在一個一模一樣的char數(shù)組闸迷。
???如果存在嵌纲,String對象會被調(diào)整引用那個數(shù)組,釋放對原來的數(shù)組的引用腥沽,最終會被垃圾收集器回收掉逮走。
???如果查找失敗,char數(shù)組會被插入到hashtable,這樣以后的時候就可以共享這個數(shù)組了今阳。
●?命令行選項(xiàng)
???UseStringDeduplication (bool) :開啟String去重师溅,默認(rèn)是不開啟的,需要手動開啟盾舌。
???PrintStringDeduplicationStatistics (bool) :打印詳細(xì)的去重統(tǒng)計(jì)信息
???StringDeduplicationAgeThreshold (uintx) :達(dá)到這個年齡的String對象被認(rèn)為是去重的候選對象