【轉】淺談String.intern()方法

轉自:https://blog.csdn.net/u011635492/article/details/81048150###

1.String類型“==”比較樣例代碼如下:
package com.luna.test;
public class StringTest {
    public static void main(String[] args) {
    String str1 = "todo";
        String str2 = "todo";
        String str3 = "to";
        String str4 = "do";
        String str5 = str3 + str4;
        String str6 = new String(str1);
 
        System.out.println("------普通String測試結果------");
        System.out.print("str1 == str2 ? ");
        System.out.println( str1 == str2);
        System.out.print("str1 == str5 ? ");
        System.out.println(str1 == str5);
        System.out.print("str1 == str6 ? ");
        System.out.print(str1 == str6);
        System.out.println();
 
        System.out.println("---------intern測試結果---------");
        System.out.print("str1.intern() == str2.intern() ? ");
        System.out.println(str1.intern() == str2.intern());
        System.out.print("str1.intern() == str5.intern() ? ");
        System.out.println(str1.intern() == str5.intern());
        System.out.print("str1.intern() == str6.intern() ? ");
        System.out.println(str1.intern() == str6.intern());
        System.out.print("str1 == str6.intern() ? ");
        System.out.println(str1 == str6.intern());
    }
}
      代碼運行結果如下所示:

------普通String測試結果------
str1 == str2 ? true
str1 == str5 ? false
str1 == str6 ? false
---------intern測試結果---------
str1.intern() == str2.intern() ? true
str1.intern() == str5.intern() ? true
str1.intern() == str6.intern() ? true
str1 == str6.intern() ? true
      普通String代碼結果分析:Java語言會使用常量池保存那些在編譯期就已確定的已編譯的class文件中的一份數據。主要有類、接口、方法中的常量,以及一些以文本形式出現的符號引用,如類和接口的全限定名、字段的名稱和描述符、方法和名稱和描述符等。因此在編譯完Intern類後,生成的class文件中會在常量池中保存“todo”、“to”和“do”三個String常量。變量str1和str2均保存的是常量池中“todo”的引用,所以str1==str2成立;在執行 str5 = str3 + str4這句時,JVM會先創建一個StringBuilder對象,通過StringBuilder.append()方法將str3與str4的值拼接,然後通過StringBuilder.toString()返回一個堆中的String對象的引用,賦值給str5,因此str1和str5指向的不是同一個String對象,str1 == str5不成立;String str6 = new String(str1)一句顯式創建了一個新的String對象,因此str1 == str6不成立便是顯而易見的事了。

2.String.intern()使用原理
      String.intern()是一個Native方法,底層調用C++的 StringTable::intern方法實現。當通過語句str.intern()調用intern()方法後,JVM 就會在當前類的常量池中查找是否存在與str等值的String,若存在則直接返回常量池中相應Strnig的引用;若不存在,則會在常量池中創建一個等值的String,然後返回這個String在常量池中的引用。因此,只要是等值的String對象,使用intern()方法返回的都是常量池中同一個String引用,所以,這些等值的String對象通過intern()後使用==是可以匹配的。由此就可以理解上面代碼中------intern------部分的結果了。因爲str1、str5和str6是三個等值的String,所以通過intern()方法,他們均會指向常量池中的同一個String引用,因此str1.intern() == str5.intern() == str6.intern()均爲true。

3.String.intern() in JDK6
      Jdk6中常量池位於PermGen(永久代)中,PermGen是一塊主要用於存放已加載的類信息和字符串池的大小固定的區域。執行intern()方法時,若常量池中不存在等值的字符串,JVM就會在常量池中創建一個等值的字符串,然後返回該字符串的引用。除此以外,JVM 會自動在常量池中保存一份之前已使用過的字符串集合。Jdk6中使用intern()方法的主要問題就在於常量池被保存在PermGen中:首先,PermGen是一塊大小固定的區域,一般不同的平臺PermGen的默認大小也不相同,大致在32M到96M之間。所以不能對不受控制的運行時字符串(如用戶輸入信息等)使用intern()方法,否則很有可能會引發PermGen內存溢出;其次String對象保存在Java堆區,Java堆區與PermGen是物理隔離的,因此如果對多個不等值的字符串對象執行intern操作,則會導致內存中存在許多重複的字符串,會造成性能損失。

4.String.intern() in JDK7
      Jdk7將常量池從PermGen區移到了Java堆區,執行intern操作時,如果常量池已經存在該字符串,則直接返回字符串引用,否則複製該字符串對象的引用到常量池中並返回。堆區的大小一般不受限,所以將常量池從PremGen區移到堆區使得常量池的使用不再受限於固定大小。除此之外,位於堆區的常量池中的對象可以被垃圾回收。當常量池中的字符串不再存在指向它的引用時,JVM就會回收該字符串。可以使用 -XX:StringTableSize 虛擬機參數設置字符串池的map大小。字符串池內部實現爲一個HashMap,所以當能夠確定程序中需要intern的字符串數目時,可以將該map的size設置爲所需數目*2(減少hash衝突),這樣就可以使得String.intern()每次都只需要常量時間和相當小的內存就能夠將一個String存入字符串池中。

5.intern()適用場景
      Jdk6中常量池位於PermGen區,大小受限,所以不建議適用intern()方法,當需要字符串池時,需要自己使用HashMap實現。Jdk7、8中,常量池由PermGen區移到了堆區,還可以通過-XX:StringTableSize參數設置StringTable的大小,常量池的使用不再受限,由此可以重新考慮使用intern()方法。intern()方法優點:執行速度非常快,直接使用==進行比較要比使用equals()方法快很多;內存佔用少。雖然intern()方法的優點看上去很誘人,但若不是在恰當的場合中使用該方法的話,便非但不能獲得如此好處,反而還可能會有性能損失。下面程序對比了使用intern()方法和未使用intern()方法存儲100萬個String時的性能,從輸出結果可以看出,若是單純使用intern()方法進行數據存儲的話,程序運行時間要遠高於未使用intern()方法時:

public class InternTest {
    public static void main(String[] args) {
        print("noIntern: " + noIntern());
        print("intern: " + intern());
    }
 
    private static long noIntern(){
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            int j = i % 100;
            String str = String.valueOf(j);
        }
        return System.currentTimeMillis() - start;
    }
 
    private static long intern(){
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            int j = i % 100;
            String str = String.valueOf(j).intern();
        }
        return System.currentTimeMillis() - start;
    }
}
      程序運行結果:

noIntern: 48    // 未使用intern方法時,存儲100萬個String所需時間
intern: 99      // 使用intern方法時,存儲100萬個String所需時間
      由於intern()操作每次都需要與常量池中的數據進行比較以查看常量池中是否存在等值數據,同時JVM需要確保常量池中的數據的唯一性,這就涉及到加鎖機制,這些操作都是有需要佔用CPU時間的,所以如果進行intern操作的是大量不會被重複利用的String的話,則有點得不償失。由此可見,String.intern()主要 適用於只有有限值,並且這些有限值會被重複利用的場景,如數據庫表中的列名、人的姓氏、編碼類型等。

6.總結:
      String.intern()方法是一種手動將字符串加入常量池中的方法,原理如下:如果在常量池中存在與調用intern()方法的字符串等值的字符串,就直接返回常量池中相應字符串的引用,否則在常量池中複製一份該字符串,並將其引用返回(Jdk7中會直接在常量池中保存當前字符串的引用);Jdk6 中常量池位於PremGen區,大小受限,不建議使用String.intern()方法,不過Jdk7 將常量池移到了Java堆區,大小可控,可以重新考慮使用String.intern()方法,但是由對比測試可知,使用該方法的耗時不容忽視,所以需要慎重考慮該方法的使用;String.intern()方法主要適用於程序中需要保存有限個會被反覆使用的值的場景,這樣可以減少內存消耗,同時在進行比較操作時減少時耗,提高程序性能。
————————————————
版權聲明:本文爲CSDN博主「抽離的心」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u011635492/article/details/81048150

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章