您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關Java字符串的intern方法有什么奧妙之處,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
進入正文學習字符串的intern()方法之前,先給下這4個問題,看下自己是否都知道答案?
1、String s1 = “a” + “b”; //創建了幾個對象?
2、String s2 = new String(“ab”); //創建了幾個對象?
3、String s3 = new String(“a”) + new String(“b”); //創建了幾個對象?
4、String s4= new String(“a”) + new String(“a”); s4.intern(); //創建了幾個對象?
如果都清楚,恭喜你,大佬一枚,不用往下學習了,哈哈哈!
那如果不太確定或者需要加深自己的理解,建議進入正文一起來了解下吧!
當然,也可以拉到最后有答案!
先來執行一下String調用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 執行輸出結果:
false
false
JDK1.7 執行輸出結果:
false
true
大家可以先思考一下為什么結果是這樣的?往下會具體介紹!
先來看一下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();
很顯然通過源碼可以看到intern()是一個native本地方法,但是native具體實現源碼已經被隱藏了,這是一個歷史故事了,SUN公司在JDK7開發期間,由于技術競爭和商業競爭陷入泥潭,無力再投入精力繼續研發JDK,Oracle半路殺出直接收購Sun公司,Oracle接管JDK的研發后,發版了自己的Oracle JDK,Oracle的native底層等很多源碼就被隱藏了,不過Oracle官方也聲明OpenJDK和Oracle JDK7及以后版本,源碼幾乎是一模一樣的,想要了解native底層源碼具體實現過程,可以下載開源的OpenJDK的源碼進行查看。
OpenJDK官網:https://hg.openjdk.java.net/
GitHub也開源啦:https://github.com/openjdk/jdk
例如String對應的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底層方法的實現,需要掌握C和C++的語法,學習門檻要求比較高,這里不是我們要學習的重點,不做具體介紹。
前面JDK源碼intern()方法的英文注釋已經說明了intern()方法的有具體用途了,網上也有很多說明,不過這里我以個人的理解以及話術簡單概括下intern()方法的作用如下:
(1)只要調用String對象的intern(),都會去找到字符串常量池,然后判斷String對象的字符串內容是否已經存在常量池中,不存在,則往字符串常量池中創建該字符串內容的對象(JDK6及之前)或創建新的引用并指向堆區已有對象地址(JDK7之后)
,存在則直接返回。
(2)JDK7時,字符串常量池從永久代脫離,遷移到堆區中,相比于JDK6,變化不只是字符串常量池遷移到堆區而已,另一個變化就是調用字符串對象的intern()方法,如果字符串常量池中不存在該字符串內容的對象,則不會再像JDK6直接往字符串常量池中創建該字符串內容的對象,而是創建一個新的引用并指向堆區已有對象地址,實現字符串常量池和堆區字符串共用的目的,效率更高。
一張圖介紹前面示例代碼JDK6執行過程如下:
/** * JDK6 String#intern()執行說明 */ public class StringInternTest { public static void main(String[] args) { //Step6.1 //創建了2個對象,分別是堆區的String對象和字符串常量池中的"a"對象,reference1引用指向在堆區中的對象地址 String reference1 = new String("a"); //Step6.2 //判斷字符串常量池,是否該字符串"a",此前,池中已經有該對象了,因此會返回池中的對象地址的引用 reference1.intern(); //Step6.3 //字符串常量池中已存在字符串"a",因此reference2引用直接指向對象在字符串常量池中的地址 String reference2 = "a"; //reference1指向對象地址是在堆區,reference2指向對象地址是在永久代的常量池,顯然不可能一樣 System.out.println(reference1 == reference2); //Step6.4 //創建了2個對象,分別是在堆區的String對象(內容是"aa")和字符串常量池中的"a"對象 //reference3引用指向對象在堆區中的地址,這過程還會在堆區創建了兩個無引用的"a"對象,這里不做討論 String reference3 = new String("a") + new String("a"); //Step6.5 //判斷永久代中的字符串常量池,是否存在該字符串"aa",這里是首次出現,因此直接將字符串拷貝并放到池中 reference3.intern(); //Step6.6 //池中已存在該字符串,reference2引用直接指向對象在永久代字符串常量池中的地址 String reference4 = "aa"; //同樣,reference3指向堆區地址,reference4指向永久代常量池中的地址,顯然不可能一樣 System.out.println(reference3 == reference4); } }
一張圖介紹前面示例代碼JDK7執行過程如下:
/** * JDK1.7 String#intern()執行說明 **/ public class StringInternTest { public static void main(String[] args) { //Step7.1 //創建了2個對象,分別是堆區的String對象和字符串常量池中的"a"對象,reference1引用指向在堆區中的對象地址 String reference1 = new String("a"); //Step7.2 //判斷字符串常量池,是否該字符串"a",此前,池中已經有該對象了,因此會返回池中的對象地址的引用 reference1.intern(); //Step7.3 //字符串常量池中已存在字符串"a",因此reference2引用直接指向對象在字符串常量池中的地址 String reference2 = "a"; //reference1指向對象地址是在堆區,reference2指向對象地址是在堆區的字符串常量池,引用指向的對象地址不一樣 System.out.println( reference1 == reference2); //Step7.4 //創建了2個對象,分別是在堆區的String對象(內容是"aa")和字符串常量池中的"a"對象(注意并不會創建"aa"對象) //reference3引用指向對象在堆區中的地址,這過程還會在堆區創建了兩個無引用的"a"對象,這里不做討論 String reference3 = new String("a") + new String("a"); //Step7.5 //判斷堆區的字符串常量池中,是否存在該字符串"aa",顯然這里是首次出現 //但并不像JDK6會新建對象"aa"存儲,而是存儲指向堆區已有對象地址的一個新引用 reference3.intern(); //Step7.6 //指向池中已有該字符串的新引用,reference4引用直接指向字符串常量池中的這個新引用,新引用則指向堆區已有對象地址 String reference4 = "aa"; //reference4指向新引用,而新引用則指向堆區已有對象地址,跟reference3引用直接指向的對象地址是同一個 System.out.println(reference3 == reference4); }
在實際的Java面試當中,經常會被問到字符串創建了幾個對象的問題,主要是考察學習者對于對象的實例化以及字符串常量池在JVM結構體系中是如何運行的,個人覺得比較常見問題,無法就是如下幾個:
1、最簡單的比如:String s1 = “a” + “b”;創建了幾個對象?
答:最多1個,多個字符串常量相加會被編譯器優化為一個字符串常量即"ab",如果字符串常量池不存在,則創建該對象。
2、相對簡單的比如:String s1 = new String(“ab”);創建了幾個對象?
答:1個或2個,使用new實例化對象,必然會在堆區創建一個對象,另外一個就是如果在字符串常量池中不存在"ab"這個對象,則會創建這個"ab"常量對象。
3、稍微難一點的比如:String s2 = new String(“a”) + new String(“b”);創建了幾個對象?
答:至少4個,最多6個
堆區的1個new StringBuilder()和2個new String()
還有1個是StringBuilder()的toString()方法底層實現是new String(value, 0, count)
另外2個即"a"、"b"可能會在常量池新建對象
有的同學可能會有疑問,那這個toString過程"ab"字符串不會在常量池中也創建嗎?
答案是,不會,最后StringBuilder的toString() 的調用,底層new String(value, 0, count)
并不會在字符串常量池中去創建"ab"對象。
兩個new String相加會被優化為StringBuilder,可以通過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 }
最難的無非就是再調用intern()方法,比如:
String s3= new String(“a”) + new String(“b”);
s3.intern();創建了幾個對象?
答:最少4個,最多7個
1個new StringBuilder()和2個new String
還有1個是StringBuilder()的toString()方法底層實現是new String(value, 0, count)
另外"a"、“b"可能會在常量池新建對象
最后調用intern()方法時,會去字符串常量池,判斷"ab"是否存在,不存在,JDK6時會創建"ab” 1個對象,JDK7則只創建"ab"的引用并指向堆區內容為"ab"的StringBuilder對象地址。
到此這篇關于Java字符串的intern方法有何奧妙之處的文章就介紹到這了,更多相關Java intern方法內容請搜索億速云以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持億速云!
看完上述內容,你們對Java字符串的intern方法有什么奧妙之處有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。