Java學習拾遺2--由String str=”abc”引出的故事

 Java學習拾遺2--由String str=”abc”引出的故事(綜合了下邊的一些指點已更新,感謝在下邊留言交流的朋友,謝謝你們的指點!) 收藏 此文於2009-11-10被推薦到CSDN首頁
如何被推薦?
話題是由如下的事情引出的:

public class StringTest { 
public static void main(String[] args) { 
String str1 = new String("abc"); 
String str2 = "abc"; 
if (str1 == str2) { 
            System.out.println("str1 == str2"); 
        } else { 
            System.out.println("str1 != str2"); 
        } 
String str3 = "abc"; 
if (str2 == str3) { 
            System.out.println("str2 == str3"); 
        } else { 
            System.out.println("str2 != str3"); 
        } 
        str1 = str1.intern(); 
if (str1 == str2) { 
            System.out.println("str1 == str2"); 
        } else { 
            System.out.println("str1 != str2"); 
        } 
String str4 = new String("abc"); 
        str4 = str4.intern(); 
if (str1 == str4) { 
            System.out.println("str1 == str4"); 
        } else { 
            System.out.println("str1 != str4"); 
        } 
    } 

這段程序的輸出是什麼?

答案:

str1 != str2 
str2 == str3 
str1 == str2 
str1 == str4 
先看看String類型的對象的產生方法:

String有一個所謂的String constant pool ,是一個特殊的一個空間(注意這個是在PermGen上的,它是JVM用來保存類定義和常量池的保留空間,缺省是64M)保存String常量。String str = “abc”是先定義一個名爲str的對String類的對象引用變量:String str;再用equals方法(String類覆蓋了equals方法)判斷這個特殊空間(String constant pool )是否有abc,有則將原來在棧中指向abc的引用賦值給str,否則就在這個特殊空間(String constant pool )上開闢一個存放字面值爲"abc"的地址,接着在堆上創建一個新的String類的對象o,並將o 的字符串值指向這個地址,而且在棧中這個地址旁邊記下這個引用的對象o。最後將str指向對象o的地址。

str(棧)->o(堆)->abc(棧)

String str2 = new String("abc")則是在普通堆上創建abc對象。所以str和str2是指向不同的對象,它們是不同的。那麼這樣話程序的3--15行就好理解了。

而String有個intern()方法,這個方法是個本地方法,調用它就會引發一個查找的過程,要有abc存在 (這裏的確有abc) ,那麼它相當於告訴JVM,已經有一個內容爲abc對象存在了,返回給我一個指向它的引用即可。所以16--27打印的結果是相等的。

intern這個方法使得建立String更加節省空間並且使用==判斷更加快速。注意,在Java 中常常自動intern,我們看下邊的這個例子。

package org.bupt.test;
public class StringInternExample {
    private static char[] chars =
        {'A', 'S', 't', 'r', 'i', 'n', 'g'};
    public static void main(String[] args) {
        // (0) 一般的,我們使用這樣的語句定義一個字符串
        String aString = "AString";
        // (1) 對於第一個例子,我們連接兩個String,他們在編譯時都是已知的。
        String aConcatentatedString = "A" + "String";
        printResults("(1)",
            "aString", aString,
            "aConcatentatedString", aConcatentatedString);//true , true
        // (2) 對於第二個例子,建立相同的String,但是它的內容直到運行時才知道
        String aRuntimeString = new String(chars);
        printResults("(2)",
            "aString", aString,
            "aRuntimeString", aRuntimeString);//false , true
        // (3) 對於第三個例子,創建String,並且調用intern
        String anInternedString = aRuntimeString.intern();
        printResults("(3)",
            "aString", aString,
            "anInternedString", anInternedString);//true , true
        // (4) 對於第四個例子,我們顯式給予其值。
        String anExplicitString = new String("AString");
        printResults("(4)",
            "aString", aString,
            "anExplicitString", anExplicitString);//false, true
        // (5) 對於第五個例子,我們看看傳入參數對這個的測試
        if (args.length > 0) {
            String firstArg = args[0];
            printResults("(5)",
                "aString", aString,
                "firstArg", firstArg);//false , false
            // (6) 對於傳入參數的intern測試
            String firstArgInterned = firstArg.intern();
            printResults("(6)",
                "aString", aString,
                "firstArgInterned", firstArgInterned);//false ,false
        }
    }
    /**
     * 打印 equals(...) 和 ==
     */
    private static void printResults(String tag,String s1Name, String s1, String s2Name, String s2) {
        System.out.println(tag);
        System.out.println("  " +s1Name + " == " + s2Name + " : " + (s1 == s2));
        System.out.println("  " +s1Name + ".equals(" + s2Name + ") : " + s1.equals(s2));
        System.out.println();
    }
}
結果如下:

(1)
  aString == aConcatentatedString : true
  aString.equals(aConcatentatedString) : true
(2)
  aString == aRuntimeString : false
  aString.equals(aRuntimeString) : true
(3)
  aString == anInternedString : true
  aString.equals(anInternedString) : true
(4)
  aString == anExplicitString : false
  aString.equals(anExplicitString) : true
(5)
  aString == firstArg : false
  aString.equals(firstArg) : true
(6)
  aString == firstArgInterned : true
  aString.equals(firstArgInterned) : true
我們可以一個個分析:

情況(1):使用了“+”這個連接字連接兩個在編譯時就已知的字符串,得到的結果是==和equals都爲true,說明+動作在編譯時就會執行。

情況(2):使用了new String(chars)創建了一個只要運行時才知道的字符串,得到的結果是==爲false,equals爲true,說明運行時才創建這個字符串。

情況(3):使用了intern方法創建了一個字符串,得到的結果是==和equals都爲true,前邊對intern分析可知intern只是返回了一個相同的引用而已。

情況(4):使用了intern方法創建了一個字符串,得到的結果是==爲false,equals爲true,說明new創建了一個新的對象。

情況(5):與情況(2)實質上相同。

情況(6):與情況(3)實質上相同。

好,這樣一來我們就基本清楚了這個==與equals的問題了。

最後提一句:當比較包裝類裏面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==,因爲==是用來判斷兩個基本數據類型是不是相同。

借用一個網友的Blog,簡單的總結一下:

Java Virtual Machine maintains an internal list of references for interned Strings ( pool of unique Strings) to avoid duplicate String objects in heap memory. Whenever the JVM loads String literal from class file and executes, it checks whether that String exists in the internal list or not. If it already exists in the list, then it  does not create a new String and it uses reference to the existing String Object. JVM does this type of checking internally for String literal but not for String object which it creates through ‘new’ keyword. You can explicitly force JVM to do this type of checking for String objects which are created through ‘new’ keyword using String.intern() method. This forces JVM to check the internal list and use the existing String object if it is already present.

So the conclusion is, JVM maintains unique String objects for String literals internally. Programmers need not bother about String literals but they should bother about String objects that are created using ‘new’ keyword and they should use intern() method to avoid duplicate String objects in heap memory which in turn improves java performance. see the following section for more information.

其中有些細節問題暫時無從求證,請帶有批判眼光看這篇小文。

我們最後判斷一下這些的問題:

1."a" == "a"
2."a"+"b" == "ab"
3. "a".toLowerCase()=="a"
4. "a"+"b".toLowerCase() == "ab"

5."A".toLowerCase() == "a"

以上這些語句用sysout輸出,哪些是true,哪些是false?

1.true,原因如上。

2.true,兩個確定的字符串常量,經編譯器優化後在class中就已經是"ab“,在編譯期其字符串常量的值就確定下來了。

3.true,如果沒變的話,返回的就是原來的字符串的對象引用。

4.false,對字符串進行小寫化操作後得到一個對象引用,再對字符串相加,存在引用的+操作在編譯期無法確定結果,即"a" +"b".toLowerCase() 無法被編譯器優化,在程序運行期,動態分配並將對象引用返回。 所以"ab"在字符串常量池裏,"a" +"b".toLowerCase() 在堆上,"=="操作符比較的是內存地址,因此比較的結果爲false.

5.false,新建一個字符串對象引用返回。

那麼就順帶說一說堆和棧。

堆是動態地分配內存大小,生存期也不必事先告訴編譯器,垃圾自動回收負責回收,但由於在運行時動態分配內存,存取速度較慢。在堆上分配空間是通過new等指令建立的,類實例化的對象就是從堆上去分配空間的。

棧中主要存放一些基本類型的變量(int、short、 long、byte、float、double、boolean、char)和對象句柄,存取速度比堆要快。注意包裝類數據,如Integer, String, Double等將相應的基本數據類型包裝起來的類。這些不是在棧上的。

另外,棧數據可以共享。

例如:

int a = 5;

int b = 5;

它的工作方式是這樣的。

JVM處理int a = 5,首先在棧上創建一個變量爲a的引用,然後去查找棧上是否還有5這個值。如果沒有找到,那麼就將5存放進來,然後將a指向5。接着處理int b = 5,在創建完b的引用後,因爲在棧中已有5這個值,便將b直接指向5。

於是,就出現了a與b同時指向5的內存地址的情況。

下午和一個線上的朋友聊起這些事情,他給了建議去看JVM的相關書籍,還介紹了用javap來看,方法都很好的。

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/gnuhpc/archive/2009/11/08/4785637.aspx

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