轉(zhuǎn)載、引用請標明出處
http://www.itdecent.cn/p/d5ecfceccccd
本文出自zhh_happig的簡書博客,謝謝
以下內(nèi)容,是本人學習的筆記和工作中的總結(jié),僅供大家參考,有誤的地方還請指正
一 String.intern
1 String為什么不可變性
- 字符串常量池的需要
- 字符串常量池的誕生是為了提升效率和減少內(nèi)存分配
- 程序有大部分時間在處理字符串,字符串很大概率會出現(xiàn)重復的情況。String的不可變性使常量池很容易被管理和優(yōu)化
- 安全性考慮
- 字符串使用頻繁,設(shè)計成不可變,有效防止字符串被有意或者無意的篡改
- String類被final修飾,同時所有的屬性都被final修飾,即不可變
- 作為HashMap、HashTable等hash型數(shù)據(jù)key的必要
2 String常量池的設(shè)計
- 字符串常量存儲在方法區(qū)的PermGen Space。在jdk1.7之后,字符串常量重新被移到了堆中
- 常量池指的是在編譯期被確定,并被保存在已編譯的.class文件中的一些數(shù)據(jù)。它包括了關(guān)于類、方法、接口等中的常量,也包括字符串常量。
- Java會確保一個常量池中相同的字符串常量有且僅有一個
3 String.intern方法
- String str="kvill" 和 String str=new String("kvill")的區(qū)別
- "kvill"都是字符串常量,它們在編譯期就被確定了, 會在常量池中創(chuàng)建一個"kvill"字符串對象
- 用new String("kvill") 創(chuàng)建的字符串不是字符串常量,不能在編譯期就確定,所以new String() 創(chuàng)建的字符串不放入常量池中,存放在堆空間
- String對象的創(chuàng)建
String str1 = new String("kvill");
String str2 = new String("kvill");
new str1時創(chuàng)建了兩個對象,先在常量池中創(chuàng)建的"kvill"對象,再在堆中創(chuàng)建string對象,注意這個創(chuàng)建的先后順序;
new str2時創(chuàng)建一個對象,堆中的另外一個string對象
String s1=new String("str") + new String("01");//會在堆中新建s1對象"str01";
String s2 = new String(s1);
這種方式創(chuàng)建s2的過程中,并不會去常量池中創(chuàng)建s1的"str01"對象,而是僅在堆里創(chuàng)建一個s2對象
示例1
String s0="kvill";
String s1="kvill";
String s2="kv" + "ill";
System.out.println( s0==s1 );//true
System.out.println( s0==s2 );//true
s0和s1中的"kvill"都是字符串常量,它們在編譯期就被確定了, 存在常量池中,且只有一個, 所以s0==s1為true
而"kv"和"ill"也都是字符串常量,編譯階段會直接合成一個字符串,進而去常量池中查找是否存在"kvill",所以s2在編譯期就被解析為一個字符串常量,它也是常量池中"kvill"的一個引用,s0==s2為true
示例2
String s0="kvill";
String s1=new String("kvill");
String s2="kv" + new String("ill");
System.out.println( s0==s1 );//false
System.out.println( s0==s2 );//false
System.out.println( s1==s2 );//false
s0還是常量池中"kvill"的引用,s1因為無法在編譯期確定,所以是運行時創(chuàng)建的新對象"kvill"的引用
s2因為有后半部分new String("ill")所以也無法在編譯期確定,所以也是一個新創(chuàng)建對象"kvill"的引用
- String.intern()方法的作用
- 在jdk1.6中,當一個String實例str調(diào)用intern()方法時,Java查找常量池中是否有相同Unicode的字符串常量,如果有,則返回常量池中字符串常量的引用,如果沒有,則在常量池中增加一個Unicode等于str的字符串并返回它的引用
- 而在jdk1.7,當一個String實例str調(diào)用intern()方法時,Java查找常量池中是否有相同Unicode的字符串常量,如果有,則返回常量池中字符串常量的引用,這一點和jdk1.6沒什區(qū)別。區(qū)別在于,如果沒有,則不會再在常量池中增加一個Unicode等于str的字符串,而只是在常量池中生成一個指向堆中的str對象的引用,并返回
示例3
String s0= "kvill";
String s1=new String("kvill");
String s2=new String("kvill");
System.out.println( s0==s1 );//false
s1.intern();
s2=s2.intern(); //把常量池中"kvill"的引用賦給s2
System.out.println( s0==s1);//false,雖然執(zhí)行了s1.intern(),但它的返回值沒有賦給s1
System.out.println( s0==s1.intern());//true, s1.intern()返回的是常量池中"kvill"的引用
System.out.println( s0==s2 );//true
二 str2.intern()在jdk7和jdk6的重點分析
1 str2.intern()在jdk7和jdk6的區(qū)別
示例4
- jdk7
String str2 = new String("str")+new String("01");//生成了3個對象, 常量池中對象"str"和"01",堆中的字符串對象"str01"
str2.intern(); //jdk1.7中,在常量池中找不到對象"str01",所以會在常量池生成一個引用,指向堆中的字符串對象"str01"
String str1 = "str01";//這句話是直接在常量池中生成"str01"對象,由于在常量池中有這么一個引用,指向堆中的字符串對象"str01",所以將這個引用給str1,而不會再在常量池中生成"str01"對象了。
System.out.println(str2==str1);//所以str1和str2是指向同一個對象,jdk1.7中返回true
- jdk6
String str2 = new String("str")+new String("01");//生成了3個對象, 常量池中對象"str"和"01",堆中的字符串對象"str01"
str2.intern(); //jdk1.6會在常量池生成一個字符串對象"str01"
String str1 = "str01";//str1指向常量池字符串對象"str01"
System.out.println(str2==str1);//由于str2和str1指向不同,jdk1.6中返回false
2 str2.intern()在jdk1.7典型問題分析
示例5
String str2 = new String("str")+new String("01");
str2.intern();
String str1 = "str01";
System.out.println(str2==str1);//jdk1.7中返回true。
調(diào)換代碼順序
String str2 = new String("str")+new String("01");//生成了3個對象, 常量池中對象"str"和"01",堆中的字符串對象"str01"
String str1 = "str01";//直接在常量池中生成"str01"對象
str2.intern(); //jdk1.7中,在常量池中已經(jīng)有對象"str01"了,返回常量池中"str01"對象的引用
System.out.println(str2==str1);//所以str1和str2是指向不同對象,返回false
示例6
String str2 = new String("str");//生成了2個對象, 常量池中對象"str",堆中的字符串對象"str"
str2.intern(); //jdk1.7中,在常量池中已經(jīng)有對象"str"了,返回常量池中"str01"對象的引用
String str1 = "str";//str1直接指向常量池中已有的"str01"對象
System.out.println(str2==str1);//所以str1和str2是指向不同對象,返回false
示例7
String s1 = new String("str");//這句代碼執(zhí)行時,先在常量池中創(chuàng)建的"str"對象,即使發(fā)現(xiàn)"str"對象不存在,也無法生成指向堆中的字符串對象s1的引用,因為此時堆中的字符串對象s1還沒有被創(chuàng)建,所以最終還是在常量池中創(chuàng)建的"str"對象。
System.out.println(s1.intern() == s1);//s1.intern()指向了常量池中的"str"對象,s1指向了堆,所以返回false
String s1=new String("str") + new String("01");//生成了3個對象,常量池中對象"str"和"01",堆中的字符串對象"str01"
System.out.println(s1.intern() == s1);//注意s1.intern(),"str01"在常量池中不存在, 所以會返回s1的引用;這里返回true,注意與上面的區(qū)別
示例8
String s1=new String("str") + new String("01");//生成了3個對象,常量池中對象"str"和"01",堆中的字符串對象"str01"
String s2 = new String(s1);//這種方式創(chuàng)建String,并不會去常量池創(chuàng)建s1中的"str01"對象,而是僅在堆里創(chuàng)建一個s2對象
System.out.println(s2.intern() == s2); //s2.intern()發(fā)現(xiàn)沒常量池中沒有"str01"對象, 將堆中s2的引用返回;結(jié)果為true
三 其他問題
String s1 = “abc”;
String s2 = “a”;
String s3 = “bc”;
String s4 = s2 + s3;
System.out.println(s1 == s4);
輸出false,因為s2+s3實際上是使用StringBuilder.append來完成,會生成不同的對象。
String s1 = “abc”;
final String s2 = “a”;
final String s3 = “bc”;
String s4 = s2 + s3;
System.out.println(s1 == s4);
輸出true,因為final變量在編譯后會直接替換成對應的值,所以實際上等于s4=“a”+”bc”,而這種情況下,編譯器會直接合并為s4=“abc”,所以最終s1==s4。
以上內(nèi)容,是本人學習的筆記和工作中的總結(jié),僅供大家參考,有誤的地方還請指正
轉(zhuǎn)載、引用請標明出處
http://www.itdecent.cn/p/d5ecfceccccd
本文出自zhh_happig的簡書博客,謝謝