Java 中 String 的 equals 與 ==

在 JAVA 語言中有8中基本類型和一種比較特殊的類型String。這些類型爲了使他們在運行過程中速度更快,更節省內存,都提供了一種常量池的概念。常量池就類似一個JAVA系統級別提供的緩存。

在 jdk6中,常量池是放在 Perm 區中的,Perm 區和正常的 JAVA Heap 區域是完全分開的。jdk7中,將String常量池 從 Perm 區移動到了 Java Heap區

8種基本類型的常量池都是系統協調的,String類型的常量池比較特殊。它的主要使用方法有兩種:

String s1= "s1";
String s2= "s"+"2";
String s3= new String("s3");
  • 直接使用雙引號聲明出來的String對象。編譯期先去字符串常量池檢查是否存在 “s1” ,如不存在則在常量池開闢一個內存空間存放 “s1”;如有則不用重新開闢空間。然後在棧中開闢一塊空間,命名爲“s1”,存放的值爲常量池中“s1”的內存地址.

  • 拼接靜態字符時,用 + 時,編譯器會對此優化,視爲 String s2 = "s2";(動態拼接時,儘量使用StringBuffer 或 StringBuilder 的 append )

  • 通過關鍵字 new 定義,String s3= new String("s3"); 這段代碼,類似於String s = "s2" ; String s2 = new String(s);首先"s3" 這裏涉及到了第一種使用雙引號聲明String 對象,先看常量池是否存在…步驟省略。然後是在 堆內存中開闢然後在內存堆中開闢一塊空間存放new出來的String實例,在棧中開闢一塊空間,命名爲“s2”,存放的值爲堆中String實例的內存地址,這個過程就是將引用s2指向new出來的String實例.
    不是用雙引號聲明的String對象,可以使用String提供的intern方法。intern 方法會從字符串常量池中查詢當前字符串是否存在,若不存在就會將當前字符串放入常量池中

關於== 與 equals

String 重寫的equals 方法:

 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                            return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

可以看到,equals 方法比較的是 value 值。== 比較的是在內存中的地址。

那麼下面的程序:

public static void main(String[] args) {

        String str1 ="monday";
        String str2 ="monday";
        String str3 ="mon"+"day";

        final String str0 = "mon";
        String str = "mon";

        String str4= str0+"day";
        String str5= str+"day";
        String str6 = new String("monday");

        if(str1==str2){
            System.out.println("str1==str2");
        }else 
        if(str1.equals(str2)){
            System.out.println("str1 only equals str2");
        }

        if(str1==str3){
            System.out.println("str1==str3");
        }else 
        if(str1.equals(str3)){
            System.out.println("str1 only equals str3");
        }

        if(str1==str4){
            System.out.println("str1==str4");
        }else 
        if(str1.equals(str4)){
            System.out.println("str1 only equals str4");
        }

        if(str1==str5){
            System.out.println("str1==str5");
        }else 
        if(str1.equals(str5)){
            System.out.println("str1 only equals str5");
        }

        if(str1==str6){
            System.out.println("str1==str6");
        }else 
        if(str1.equals(str6)){
            System.out.println("str1 only equals str6");
        }

    }

由於字符串常量池共享的特性,可以知道結果:

str1==str2
str1==str3
str1==str4
str1 only equals str5
str1 only equals str6

str1 ,str2 都是在常量池中,指向的是同一個對象,str3 編譯期優化之後也相同。因此這個三個可以通過 == 來判斷。str0 是final 修飾,編譯期被解析爲常量,因此 str4 與 str3 是相同的。str5 是由非靜態字符拼接的,編譯期無法確定.
看到這裏可以知道對於字符串含有不確定量的字符拼接,其實是通過額外創建一個StringBuffer,通過append 方法來實現的,然後再通過 toString 方法轉化爲String。因此肯定不是指向的同一個地址。

String 中的 intern

使用String提供的intern方法時,intern 方法會從字符串常量池中查詢當前字符串是否存在,若不存在就會將當前字符串放入常量池中。intern 作用是:“如果常量池中存在當前字符串, 就會直接返回當前字符串. 如果常量池中沒有此字符串, 會將此字符串放入常量池中後, 再返回”。

要注意的是,String的 String Pool 是一個固定大小的Hashtable,默認值大小長度是1009,如果放進String Pool的String非常多,就會造成Hash衝突嚴重,從而導致鏈表會很長,而鏈表長了後直接會造成的影響就是當調用String.intern時性能會大幅下降(因爲要一個一個找)。

jdk6中StringTable是固定的,就是1009的長度,所以如果常量池中的字符串過多就會導致效率下降很快。在jdk7中,StringTable的長度可以通過一個參數指定:-XX:StringTableSize=99991

    String s = new String("1");
        String s2 = "1";
        System.out.println(s.intern() == s2);

        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);

上述代碼片段中,第一個可以知道是true ,因爲s.intern() 返回的是字符串常量池中的字符串。但是第二個是在不懂。。。。原文的解釋是

在第一段代碼中,先看 s3和s4字符串。String s3 = new String(“1”) + new String(“1”);,這句代碼中現在生成了2最終個對象,是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的對象。中間還有2個匿名的new String(“1”)我們不去討論它們。此時s3引用對象內容是”11”,但此時常量池中是沒有 “11”對象的。
接下來s3.intern();這一句代碼,是將 s3中的“11”字符串放入 String 常量池中,因爲此時常量池中不存在“11”字符串,因此常規做法是跟 jdk6 圖中表示的那樣,在常量池中生成一個 “11” 的對象,關鍵點是 jdk7 中常量池不在 Perm 區域了,這塊做了調整。常量池中不需要再存儲一份對象了,可以直接存儲堆中的引用。這份引用指向 s3 引用的對象。 也就是說引用地址是相同的。

看不懂。。。。。
關於 intern 方法的使用:

在Java6底下大量使用intern()會導致應用性能的顯著下降,還有可能產生OOM錯誤。但從Java7開始,字符串常量池被移到了Heap空間,Heap空間的大小隻受制於機器的真實內存大小,因此,在Java7下使用String.intern()能更有效地減少重複String對象對內存的佔用。

參考:
http://tech.meituan.com/in_depth_understanding_string_intern.html
http://www.importnew.com/15397.html
http://blog.csdn.net/gaopeng0071/article/details/11741027
http://my.oschina.net/xiaohui249/blog/170013?p=2#comments

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