轉(zhuǎn)載启盛、引用請標(biāo)明出處
http://www.reibang.com/p/d5ecfceccccd
本文出自zhh_happig的簡書博客,謝謝
以下內(nèi)容,是本人學(xué)習(xí)的筆記和工作中的總結(jié),僅供大家參考备畦,有誤的地方還請指正
一 String.intern
1 String為什么不可變性
- 字符串常量池的需要
- 字符串常量池的誕生是為了提升效率和減少內(nèi)存分配
- 程序有大部分時(shí)間在處理字符串,字符串很大概率會出現(xiàn)重復(fù)的情況许昨。String的不可變性使常量池很容易被管理和優(yōu)化
- 安全性考慮
- 字符串使用頻繁懂盐,設(shè)計(jì)成不可變,有效防止字符串被有意或者無意的篡改
- String類被final修飾糕档,同時(shí)所有的屬性都被final修飾莉恼,即不可變
- 作為HashMap、HashTable等hash型數(shù)據(jù)key的必要
2 String常量池的設(shè)計(jì)
- 字符串常量存儲在方法區(qū)的PermGen Space。在jdk1.7之后类垫,字符串常量重新被移到了堆中
- 常量池指的是在編譯期被確定司光,并被保存在已編譯的.class文件中的一些數(shù)據(jù)。它包括了關(guān)于類悉患、方法残家、接口等中的常量,也包括字符串常量售躁。
- Java會確保一個(gè)常量池中相同的字符串常量有且僅有一個(gè)
3 String.intern方法
- String str="kvill" 和 String str=new String("kvill")的區(qū)別
- "kvill"都是字符串常量坞淮,它們在編譯期就被確定了, 會在常量池中創(chuàng)建一個(gè)"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時(shí)創(chuàng)建了兩個(gè)對象市袖,先在常量池中創(chuàng)建的"kvill"對象啡直,再在堆中創(chuàng)建string對象,注意這個(gè)創(chuàng)建的先后順序;
new str2時(shí)創(chuàng)建一個(gè)對象苍碟,堆中的另外一個(gè)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)建一個(gè)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"都是字符串常量微峰,它們在編譯期就被確定了, 存在常量池中舷丹,且只有一個(gè), 所以s0==s1為true
而"kv"和"ill"也都是字符串常量,編譯階段會直接合成一個(gè)字符串蜓肆,進(jìn)而去常量池中查找是否存在"kvill"颜凯,所以s2在編譯期就被解析為一個(gè)字符串常量,它也是常量池中"kvill"的一個(gè)引用仗扬,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因?yàn)闊o法在編譯期確定,所以是運(yùn)行時(shí)創(chuàng)建的新對象"kvill"的引用
s2因?yàn)橛泻蟀氩糠謓ew String("ill")所以也無法在編譯期確定厉颤,所以也是一個(gè)新創(chuàng)建對象"kvill"的引用
- String.intern()方法的作用
- 在jdk1.6中穴豫,當(dāng)一個(gè)String實(shí)例str調(diào)用intern()方法時(shí)凡简,Java查找常量池中是否有相同Unicode的字符串常量逼友,如果有,則返回常量池中字符串常量的引用秤涩,如果沒有帜乞,則在常量池中增加一個(gè)Unicode等于str的字符串并返回它的引用
- 而在jdk1.7,當(dāng)一個(gè)String實(shí)例str調(diào)用intern()方法時(shí)筐眷,Java查找常量池中是否有相同Unicode的字符串常量黎烈,如果有,則返回常量池中字符串常量的引用,這一點(diǎn)和jdk1.6沒什區(qū)別照棋。區(qū)別在于资溃,如果沒有,則不會再在常量池中增加一個(gè)Unicode等于str的字符串烈炭,而只是在常量池中生成一個(gè)指向堆中的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的重點(diǎn)分析
1 str2.intern()在jdk7和jdk6的區(qū)別
示例4
- jdk7
String str2 = new String("str")+new String("01");//生成了3個(gè)對象, 常量池中對象"str"和"01",堆中的字符串對象"str01"
str2.intern(); //jdk1.7中符隙,在常量池中找不到對象"str01"趴捅,所以會在常量池生成一個(gè)引用,指向堆中的字符串對象"str01"
String str1 = "str01";//這句話是直接在常量池中生成"str01"對象霹疫,由于在常量池中有這么一個(gè)引用拱绑,指向堆中的字符串對象"str01",所以將這個(gè)引用給str1丽蝎,而不會再在常量池中生成"str01"對象了猎拨。
System.out.println(str2==str1);//所以str1和str2是指向同一個(gè)對象,jdk1.7中返回true
- jdk6
String str2 = new String("str")+new String("01");//生成了3個(gè)對象, 常量池中對象"str"和"01"屠阻,堆中的字符串對象"str01"
str2.intern(); //jdk1.6會在常量池生成一個(gè)字符串對象"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個(gè)對象, 常量池中對象"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個(gè)對象, 常量池中對象"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í)行時(shí),先在常量池中創(chuàng)建的"str"對象丁眼,即使發(fā)現(xiàn)"str"對象不存在筷凤,也無法生成指向堆中的字符串對象s1的引用,因?yàn)榇藭r(shí)堆中的字符串對象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個(gè)對象卢厂,常量池中對象"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個(gè)對象,常量池中對象"str"和"01"融柬,堆中的字符串對象"str01"
String s2 = new String(s1);//這種方式創(chuàng)建String死嗦,并不會去常量池創(chuàng)建s1中的"str01"對象,而是僅在堆里創(chuàng)建一個(gè)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越走,因?yàn)閟2+s3實(shí)際上是使用StringBuilder.append來完成,會生成不同的對象靠欢。
String s1 = “abc”;
final String s2 = “a”;
final String s3 = “bc”;
String s4 = s2 + s3;
System.out.println(s1 == s4);
輸出true廊敌,因?yàn)閒inal變量在編譯后會直接替換成對應(yīng)的值,所以實(shí)際上等于s4=“a”+”bc”门怪,而這種情況下骡澈,編譯器會直接合并為s4=“abc”,所以最終s1==s4掷空。
以上內(nèi)容肋殴,是本人學(xué)習(xí)的筆記和工作中的總結(jié),僅供大家參考坦弟,有誤的地方還請指正
轉(zhuǎn)載护锤、引用請標(biāo)明出處
http://www.reibang.com/p/d5ecfceccccd
本文出自zhh_happig的簡書博客,謝謝