String類中intern方法的原理分析
- 2019 年 10 月 3 日
- 筆記
一,前言
昨天簡單整理了JVM記憶體分配和String類常用方法,遇到了String中的intern()方法。本來想一併總結起來,但是intern方法還涉及到JDK版本的問題,內容也相對較多,所以今天就彌補昨天缺失的知識點。
二,String.intern()
先來看下網上流行的關於intern()方法的示例程式碼:
public static void main(String[] args) { String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); }
列印結果是:
JDK6:false,false
JDK7:false,true
首先我們先來回顧一個概念:
- jdk6及以前中常量池是存放在方法區中。
- jdk7常量池移動到堆中。
為什麼要移動?常量池主要是存儲載入類的資訊,編譯器生成的字面量和符號引用等。它的大小大約只有4M,如果大量使用intern()方法,便會造成常量池內除溢出的情況,同時GC回收也有一定的困難度。而且在jdk8以後,連Perm區域都沒有了,被替換成了元區域。
接著我們再來說說上面兩種情況不同的結果。
JDK6:
String s = new String("1");
1,這句程式碼實際上是生成兩個實例對象,先在常量池中聲明為1的字元對象,再通過關鍵字new生成1的字元對象並被s引用。
2,接著s.intern();
先在常量池中查找是否有相應的字元串存在,如果有,直接返回引用,否則,在常量池中生成相應的字元串並返回引用。
3,而String s2 = "1";
是屬於符號引用,直接在常量池中生成。
4,最後判斷s 是否與s2相等,通過上面的分析,s指向的是通過關鍵字new出來的在堆中的字元對象,而s2是符號引用的在常量池中的字元對象,所以返回的結果為false。如下圖所示:
String s3 = new String("1") + new String("1");
同理這句程式碼實際上也是產生2個字元對象,其原理和上面分析是一樣的,而最後判斷s3與s4的值,結果還是一個堆中的s3與常量池中的s4比較,那返回的結果必然是false。
JDK7:
String s1 = new String("1"); s1.intern(); String s2 = "1"; System.out.println(s == s2);
1,這一部分程式碼與jdk6中的分析是類似的,但唯一不同的是s1.intern();
返回的是在常量池中的引用,並不會在常量池中複製一份再返回引用。
String s2 = "1";
與s1.intern();
指向的是同一個引用。
2,最後比較s與s2的值,s是堆中,s2是常量池中,因而返回結果為false。
3,接著我們再來看下面一段程式碼:
String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4);
我們先來分析第一句程式碼,同樣是生成兩個字元對象,先在常量池中生成為1的字元對象,再在堆中生成s3為11的字元對象。接著s3.intern();
執行便去常量池中查詢是否存在為11的字元對象,但是發現沒有。這個時候在jdk7中便不會再複製一份為11到常量池中,而是直接返回堆中11的字元對象的引用。所以它與s3的引用地址是相同的。
String s4 = "11";
這句程式碼是符號引用,直接去常量池中創建,但是發現池中已經存在為11的字元對象的引用,因此直接返回該引用。最後判斷s3與s4是否相等,因為它們之間的引用的相同的,所以返回的結果為true。如下圖所示:
三,補充內容
本以為寫到這裡就結束了,沒想到一位評論者告訴我在jdk8中,對於intern方法的處理與jdk7是不一樣的。在這裡非常感謝,是我的疏忽。
下面請看jdk8中關於intern方法的源碼解釋:
/** * 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. */
大致解釋一下就是說,當調用intern()方法時,如果池已經包含string這個對象時,由equals(Object)方法去比較,則池中的字元串為返回。否則,將此String對象添加到池和對這個String對象的引用被返回。
四,總結
1,對於常量池的概念要注意jdk6與jdk7之間的變化。
2,intern()方法在不同jdk版本之間的變化,jdk7中如果常量池中不存在,不會再複製一份到常量池中,而是返回堆中的存在的引用地址。
3,jdk6常量池在方法區中,jdk7常量池在堆中,jdk8取消方法區,替換成元區域。
最後關於String類中intern方法就介紹這裡,其實從實際開發工作中,我們只需要了解jdk7及以後版本的原理就可以了,畢竟現在市面上很少再用jdk6版本。但是這裡總結出來也是為了更方便的參照理解,同時也涉及到一些關於JVM記憶體的相關知識,本篇部落格是接著上一篇內容的缺失的知識點。
以上內容均是自主學習總結的,如有不適之處還請留言(郵箱)指教。
感謝閱讀!