Java字符串的intern方法有何奧妙之處
學(xué)習(xí)背景
進(jìn)入正文學(xué)習(xí)字符串的intern()方法之前,先給下這4個(gè)問(wèn)題,看下自己是否都知道答案?
1、String s1 = “a” + “b”; //創(chuàng)建了幾個(gè)對(duì)象?
2、String s2 = new String(“ab”); //創(chuàng)建了幾個(gè)對(duì)象?
3、String s3 = new String(“a”) + new String(“b”); //創(chuàng)建了幾個(gè)對(duì)象?
4、String s4= new String(“a”) + new String(“a”); s4.intern(); //創(chuàng)建了幾個(gè)對(duì)象?
如果都清楚,恭喜你,大佬一枚,不用往下學(xué)習(xí)了,哈哈哈!
那如果不太確定或者需要加深自己的理解,建議進(jìn)入正文一起來(lái)了解下吧!
當(dāng)然,也可以拉到最后有答案!
String#intern()示例代碼
先來(lái)執(zhí)行一下String調(diào)用intern()方法的一段示例代碼:
public class StringInternTest { public static void main(String[] args) { String reference1 = new String("a"); reference1.intern(); String reference2 = "a"; System.out.println(reference1 == reference2); String reference3 = new String("a") + new String("a"); reference3.intern(); String reference4 = "aa"; System.out.println(reference3 == reference4); } }
JDK1.6 執(zhí)行輸出結(jié)果:
false
false
JDK1.7 執(zhí)行輸出結(jié)果:
false
true
大家可以先思考一下為什么結(jié)果是這樣的?往下會(huì)具體介紹!
String##intern()源碼
先來(lái)看一下intern()方法的JDK源碼如下:
/** * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class {@code String}. * <p> * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. * <p> * It follows that for any two strings {@code s} and {@code t}, * {@code s.intern() == t.intern()} is {@code true} * if and only if {@code s.equals(t)} is {@code true}. * <p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java™ Language Specification</cite>. * * @return a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. */ public native String intern();
很顯然通過(guò)源碼可以看到intern()是一個(gè)native本地方法,但是native具體實(shí)現(xiàn)源碼已經(jīng)被隱藏了,這是一個(gè)歷史故事了,SUN公司在JDK7開(kāi)發(fā)期間,由于技術(shù)競(jìng)爭(zhēng)和商業(yè)競(jìng)爭(zhēng)陷入泥潭,無(wú)力再投入精力繼續(xù)研發(fā)JDK,Oracle半路殺出直接收購(gòu)Sun公司,Oracle接管JDK的研發(fā)后,發(fā)版了自己的Oracle JDK,Oracle的native底層等很多源碼就被隱藏了,不過(guò)Oracle官方也聲明OpenJDK和Oracle JDK7及以后版本,源碼幾乎是一模一樣的,想要了解native底層源碼具體實(shí)現(xiàn)過(guò)程,可以下載開(kāi)源的OpenJDK的源碼進(jìn)行查看。
OpenJDK官網(wǎng):https://hg.openjdk.java.net/
GitHub也開(kāi)源啦:https://github.com/openjdk/jdk
例如String對(duì)應(yīng)的OpenJDK底層源碼主入口:jdk7\jdk\src\share\native\java\lang\String.c
Java_java_lang_String_intern(JNIEnv *env, jobject this) { return JVM_InternString(env, this); }
native底層方法的實(shí)現(xiàn),需要掌握C和C++的語(yǔ)法,學(xué)習(xí)門檻要求比較高,這里不是我們要學(xué)習(xí)的重點(diǎn),不做具體介紹。
String#intern()方法作用
前面JDK源碼intern()方法的英文注釋已經(jīng)說(shuō)明了intern()方法的有具體用途了,網(wǎng)上也有很多說(shuō)明,不過(guò)這里我以個(gè)人的理解以及話術(shù)簡(jiǎn)單概括下intern()方法的作用如下:
(1)只要調(diào)用String對(duì)象的intern(),都會(huì)去找到字符串常量池,然后判斷String對(duì)象的字符串內(nèi)容是否已經(jīng)存在常量池中,不存在,則往字符串常量池中創(chuàng)建該字符串內(nèi)容的對(duì)象(JDK6及之前)或創(chuàng)建新的引用并指向堆區(qū)已有對(duì)象地址(JDK7之后)
,存在則直接返回。
(2)JDK7時(shí),字符串常量池從永久代脫離,遷移到堆區(qū)中,相比于JDK6,變化不只是字符串常量池遷移到堆區(qū)而已,另一個(gè)變化就是調(diào)用字符串對(duì)象的intern()方法,如果字符串常量池中不存在該字符串內(nèi)容的對(duì)象,則不會(huì)再像JDK6直接往字符串常量池中創(chuàng)建該字符串內(nèi)容的對(duì)象,而是創(chuàng)建一個(gè)新的引用并指向堆區(qū)已有對(duì)象地址,實(shí)現(xiàn)字符串常量池和堆區(qū)字符串共用的目的,效率更高。
JDK6 String#intern()執(zhí)行說(shuō)明
一張圖介紹前面示例代碼JDK6執(zhí)行過(guò)程如下:
/** * JDK6 String#intern()執(zhí)行說(shuō)明 */ public class StringInternTest { public static void main(String[] args) { //Step6.1 //創(chuàng)建了2個(gè)對(duì)象,分別是堆區(qū)的String對(duì)象和字符串常量池中的"a"對(duì)象,reference1引用指向在堆區(qū)中的對(duì)象地址 String reference1 = new String("a"); //Step6.2 //判斷字符串常量池,是否該字符串"a",此前,池中已經(jīng)有該對(duì)象了,因此會(huì)返回池中的對(duì)象地址的引用 reference1.intern(); //Step6.3 //字符串常量池中已存在字符串"a",因此reference2引用直接指向?qū)ο笤谧址A砍刂械牡刂? String reference2 = "a"; //reference1指向?qū)ο蟮刂肥窃诙褏^(qū),reference2指向?qū)ο蟮刂肥窃谟谰么某A砍?,顯然不可能一樣 System.out.println(reference1 == reference2); //Step6.4 //創(chuàng)建了2個(gè)對(duì)象,分別是在堆區(qū)的String對(duì)象(內(nèi)容是"aa")和字符串常量池中的"a"對(duì)象 //reference3引用指向?qū)ο笤诙褏^(qū)中的地址,這過(guò)程還會(huì)在堆區(qū)創(chuàng)建了兩個(gè)無(wú)引用的"a"對(duì)象,這里不做討論 String reference3 = new String("a") + new String("a"); //Step6.5 //判斷永久代中的字符串常量池,是否存在該字符串"aa",這里是首次出現(xiàn),因此直接將字符串拷貝并放到池中 reference3.intern(); //Step6.6 //池中已存在該字符串,reference2引用直接指向?qū)ο笤谟谰么址A砍刂械牡刂? String reference4 = "aa"; //同樣,reference3指向堆區(qū)地址,reference4指向永久代常量池中的地址,顯然不可能一樣 System.out.println(reference3 == reference4); } }
JDK7 String#intern()執(zhí)行說(shuō)明
一張圖介紹前面示例代碼JDK7執(zhí)行過(guò)程如下:
/** * JDK1.7 String#intern()執(zhí)行說(shuō)明 **/ public class StringInternTest { public static void main(String[] args) { //Step7.1 //創(chuàng)建了2個(gè)對(duì)象,分別是堆區(qū)的String對(duì)象和字符串常量池中的"a"對(duì)象,reference1引用指向在堆區(qū)中的對(duì)象地址 String reference1 = new String("a"); //Step7.2 //判斷字符串常量池,是否該字符串"a",此前,池中已經(jīng)有該對(duì)象了,因此會(huì)返回池中的對(duì)象地址的引用 reference1.intern(); //Step7.3 //字符串常量池中已存在字符串"a",因此reference2引用直接指向?qū)ο笤谧址A砍刂械牡刂? String reference2 = "a"; //reference1指向?qū)ο蟮刂肥窃诙褏^(qū),reference2指向?qū)ο蟮刂肥窃诙褏^(qū)的字符串常量池,引用指向的對(duì)象地址不一樣 System.out.println( reference1 == reference2); //Step7.4 //創(chuàng)建了2個(gè)對(duì)象,分別是在堆區(qū)的String對(duì)象(內(nèi)容是"aa")和字符串常量池中的"a"對(duì)象(注意并不會(huì)創(chuàng)建"aa"對(duì)象) //reference3引用指向?qū)ο笤诙褏^(qū)中的地址,這過(guò)程還會(huì)在堆區(qū)創(chuàng)建了兩個(gè)無(wú)引用的"a"對(duì)象,這里不做討論 String reference3 = new String("a") + new String("a"); //Step7.5 //判斷堆區(qū)的字符串常量池中,是否存在該字符串"aa",顯然這里是首次出現(xiàn) //但并不像JDK6會(huì)新建對(duì)象"aa"存儲(chǔ),而是存儲(chǔ)指向堆區(qū)已有對(duì)象地址的一個(gè)新引用 reference3.intern(); //Step7.6 //指向池中已有該字符串的新引用,reference4引用直接指向字符串常量池中的這個(gè)新引用,新引用則指向堆區(qū)已有對(duì)象地址 String reference4 = "aa"; //reference4指向新引用,而新引用則指向堆區(qū)已有對(duì)象地址,跟reference3引用直接指向的對(duì)象地址是同一個(gè) System.out.println(reference3 == reference4); }
經(jīng)典面試問(wèn)題之創(chuàng)建了幾個(gè)對(duì)象?
在實(shí)際的Java面試當(dāng)中,經(jīng)常會(huì)被問(wèn)到字符串創(chuàng)建了幾個(gè)對(duì)象的問(wèn)題,主要是考察學(xué)習(xí)者對(duì)于對(duì)象的實(shí)例化以及字符串常量池在JVM結(jié)構(gòu)體系中是如何運(yùn)行的,個(gè)人覺(jué)得比較常見(jiàn)問(wèn)題,無(wú)法就是如下幾個(gè):
1、最簡(jiǎn)單的比如:String s1 = “a” + “b”;創(chuàng)建了幾個(gè)對(duì)象?
答:最多1個(gè),多個(gè)字符串常量相加會(huì)被編譯器優(yōu)化為一個(gè)字符串常量即"ab",如果字符串常量池不存在,則創(chuàng)建該對(duì)象。
2、相對(duì)簡(jiǎn)單的比如:String s1 = new String(“ab”);創(chuàng)建了幾個(gè)對(duì)象?
答:1個(gè)或2個(gè),使用new實(shí)例化對(duì)象,必然會(huì)在堆區(qū)創(chuàng)建一個(gè)對(duì)象,另外一個(gè)就是如果在字符串常量池中不存在"ab"這個(gè)對(duì)象,則會(huì)創(chuàng)建這個(gè)"ab"常量對(duì)象。
3、稍微難一點(diǎn)的比如:String s2 = new String(“a”) + new String(“b”);創(chuàng)建了幾個(gè)對(duì)象?
答:至少4個(gè),最多6個(gè)
堆區(qū)的1個(gè)new StringBuilder()和2個(gè)new String()
還有1個(gè)是StringBuilder()的toString()方法底層實(shí)現(xiàn)是new String(value, 0, count)
另外2個(gè)即"a"、"b"可能會(huì)在常量池新建對(duì)象
有的同學(xué)可能會(huì)有疑問(wèn),那這個(gè)toString過(guò)程"ab"字符串不會(huì)在常量池中也創(chuàng)建嗎?
答案是,不會(huì),最后StringBuilder的toString() 的調(diào)用,底層new String(value, 0, count)
并不會(huì)在字符串常量池中去創(chuàng)建"ab"對(duì)象。
兩個(gè)new String相加會(huì)被優(yōu)化為StringBuilder,可以通過(guò)javac和javap查看匯編指令如下:
javac InternTest.java
javap -c InternTest
public class com.justin.java.lang.InternTest { public com.justin.java.lang.InternTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/StringBuilder 3: dup 4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V 7: new #4 // class java/lang/String 10: dup 11: ldc #5 // String a 13: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V 16: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: new #4 // class java/lang/String 22: dup 23: ldc #8 // String b 25: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V 28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 31: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 34: astore_1 35: return }
最難的無(wú)非就是再調(diào)用intern()方法,比如:
String s3= new String(“a”) + new String(“b”);
s3.intern();創(chuàng)建了幾個(gè)對(duì)象?
答:最少4個(gè),最多7個(gè)
1個(gè)new StringBuilder()和2個(gè)new String
還有1個(gè)是StringBuilder()的toString()方法底層實(shí)現(xiàn)是new String(value, 0, count)
另外"a"、“b"可能會(huì)在常量池新建對(duì)象
最后調(diào)用intern()方法時(shí),會(huì)去字符串常量池,判斷"ab"是否存在,不存在,JDK6時(shí)會(huì)創(chuàng)建"ab” 1個(gè)對(duì)象,JDK7則只創(chuàng)建"ab"的引用并指向堆區(qū)內(nèi)容為"ab"的StringBuilder對(duì)象地址。
到此這篇關(guān)于Java字符串的intern方法有何奧妙之處的文章就介紹到這了,更多相關(guān)Java intern方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis JdbcType 與Oracle、MySql數(shù)據(jù)類型對(duì)應(yīng)關(guān)系說(shuō)明
這篇文章主要介紹了MyBatis JdbcType 與Oracle、MySql數(shù)據(jù)類型對(duì)應(yīng)關(guān)系說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09springboot整合easy-es實(shí)現(xiàn)數(shù)據(jù)的增刪改查的示例代碼
Easy-Es是一款基于ElasticSearch官方提供的RestHighLevelClient打造的低碼開(kāi)發(fā)框架,本文主要介紹了springboot整合easy-es實(shí)現(xiàn)數(shù)據(jù)的增刪改查的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03Java集合中的WeakHashMap、IdentityHashMap、EnumMap詳解
這篇文章主要介紹了Java集合中的WeakHashMap、IdentityHashMap、EnumMap詳解,HashMap的key保留了對(duì)實(shí)際對(duì)象的強(qiáng)引用,這意味著只要HashMap對(duì)象不被銷毀,還HashMap的所有key所引用的對(duì)象就不會(huì)被垃圾回收,需要的朋友可以參考下2023-09-09Java?Apache?common-pool對(duì)象池介紹
這篇文章主要介紹了Java Apache?common-pool對(duì)象池介紹,文章通過(guò)圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-09-09SpringBoot使用@EnableAutoConfiguration實(shí)現(xiàn)自動(dòng)配置詳解
你有想過(guò)SpringBoot為什么能夠自動(dòng)的幫我們創(chuàng)建一個(gè)Bean對(duì)象么?或許在我們使用的時(shí)候只需要在自己自定義的配置文件中加入@Bean對(duì)象就可以,但SpringBoot是如何來(lái)創(chuàng)建的呢2022-08-08JAVA基礎(chǔ)之控制臺(tái)輸入輸出的實(shí)例代碼
下面小編就為大家?guī)?lái)一篇JAVA基礎(chǔ)之控制臺(tái)輸入輸出的實(shí)例代碼。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-07-07解決mybatis竟然報(bào)Invalid value for getInt()的問(wèn)題
使用mybatis遇到一個(gè)非常奇葩的問(wèn)題,總是報(bào)Invalid value for getInt()的問(wèn)題,怎么解決呢?下面小編通過(guò)場(chǎng)景分析給大家代來(lái)了mybatis報(bào)Invalid value for getInt()的解決方法,感興趣的朋友參考下吧2021-10-10Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(49)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-08-08