2011年3月5日 星期六

java String深入constant pool

聲明 來源於:http://hi.baidu.com/boywell/blog/item/a069bccbc45e7c4cf31fe758.html

Constant Pool常量池的概念:

在講到String的一些特殊情況時,總會提到String Pool或者Constant Pool,但是我想很多人都不太
明白Constant Pool到底是個怎麼樣的東西,運行的時候存儲在哪裡,所以在這裡先說一下Constant Pool的內容.
String Pool是對應於在Constant Pool中存儲String常量的區域.習慣稱為String Pool,也有人稱為
String Constant Pool.好像沒有正式的命名??

在java編譯好的class文件中,有個區域稱為Constant Pool,他是一個由數組組成的表,類型
為cp_info constant_pool[],用來存儲程序中使用的各種常量,包括Class/String/Integer等各
種基本Java數據類型,詳情參見The Java Virtual Machine Specification 4.4章節.


關於String類的說明
1.String使用private final char value[]來實現字符串的存儲,也就是說String對象創建之後,就不能
再修改此對象中存儲的字符串內容,就是因為如此,才說String類型是不可變的(immutable).

2.String類有一個特殊的創建方法,就是使用""雙引號來創建.例如new String("i am")實際創建了2個
String對象,一個是"i am"通過""雙引號創建的,另一個是通過new創建的.只不過他們創建的時期不同,
一個是編譯期,一個是運行期!

3.java對String類型重載了+操作符,可以直接使用+對兩個字符串進行連接.

4.運行期調用String類的intern()方法可以向String Pool中動態添加對象.

String的創建方法一般有如下幾種
1.直接使用""引號創建.
2.使用new String()創建.
3.使用new String("someString")創建以及其他的一些重載構造函數創建.
4.使用重載的字符串連接操作符+創建.

例1
    /*
    * "sss111"是編譯期常量,編譯時已經能確定它的值,在編譯
    * 好的class文件中它已經在String Pool中了,此語句會在
    * String Pool中查找等於"sss111"的字符串(用equals(Object)方法確定),
    * 如果存在就把引用返回,付值給s1.不存在就會創建一個"sss111"放在
    * String Pool中,然後把引用返回,付值給s1.
    *
    */
    String s1 = "sss111";

    //此語句同上
    String s2 = "sss111";

    /*
    * 由於String Pool只會維護一個值相同的String對象
    * 上面2句得到的引用是String Pool中同一個對象,所以
    * 他們引用相等
    */
    System.out.println(s1 == s2); //結果為true


例2
    /*
    * 在java中,使用new關鍵字會創建一個新對象,在本例中,不管在
    * String Pool中是否已經有值相同的對象,都會創建了一個新的
    * String對象存儲在heap中,然後把引用返回賦給s1.
    * 本例中使用了String的public String(String original)構造函數.
    */
    String s1 = new String("sss111");
 
    /*
     * 此句會按照例1中所述在String Pool中查找
     */
    String s2 = "sss111";
 
    /*
     * 由於s1是new出的新對象,存儲在heap中,s2指向的對象
     * 存儲在String Pool中,他們肯定不是同一個對象,只是
     * 存儲的字符串值相同,所以返回false.
     */
    System.out.println(s1 == s2); //結果為false


例3
    String s1 = new String("sss111");
    /*
    * 當調用intern方法時,如果String Pool中已經包含一個等於此String對象
    * 的字符串(用 equals(Object)方法確定),則返回池中的字符串.否則,將此
    * String對象添加到池中,並返回此String對象在String Pool中的引用.
    */
    s1 = s1.intern();
 
    String s2 = "sss111";
 
    /*
     * 由於執行了s1 = s1.intern(),會使s1指向String Pool中值為"sss111"
     * 的字符串對象,s2也指向了同樣的對象,所以結果為true
     */
    System.out.println(s1 == s2);


例4
    String s1 = new String("111");
    String s2 = "sss111";
 
/*
    * 由於進行連接的2個字符串都是常量,編譯期就能確定連接後的值了,
    * 編譯器會進行優化直接把他們表示成"sss111"存儲到String Pool中,
    * 由於上邊的s2="sss111"已經在String Pool中加入了"sss111",
    * 此句會把s3指向和s2相同的對象,所以他們引用相同.此時"sss"和"111"
    * 兩個常量不會再創建.
    */

    String s3 = "sss" + "111";
 
    /*
     * 由於s1是個變量,在編譯期不能確定它的值是多少,所以
     * 會在執行的時候創建一個新的String對象存儲到heap中,
     * 然後賦值給s4.
     */
    String s4 = "sss" + s1;
 
    System.out.println(s2 == s3); //true
    System.out.println(s2 == s4); //false
    System.out.println(s2 == s4.intern()); //true


例5
這個是The Java Language Specification中3.10.5節的例子,有了上面的說明,這個應該不難理解了
    package testPackage;
    class Test {
            public static void main(String[] args) {
                    String hello = "Hello", lo = "lo";
                    System.out.print((hello == "Hello") + " ");
                    System.out.print((Other.hello == hello) + " ");
                    System.out.print((other.Other.hello == hello) + " ");
                    System.out.print((hello == ("Hel"+"lo")) + " ");
                    System.out.print((hello == ("Hel"+lo)) + " "); //lo在runtime會創建一個新的對象
                    System.out.println(hello == ("Hel"+lo).intern());
            }
    }
    class Other { static String hello = "Hello"; }

    package other;
    public class Other { static String hello = "Hello"; }

輸出結果為true true true true false true,請自行分析!


結果上面分析,總結如下:
1.單獨使用""引號創建的字符串都是常量,編譯期就已經確定存儲到String Pool中.
2.使用new String("")創建的對象會存儲到heap中,是運行期新創建的.
3.使用只包含常量的字符串連接符如"aa" + "aa"創建的也是常量,編譯期就能確定,已經確定存儲到String Pool中.(編譯時會直接優化成"aaaa",如果String Pool 中沒有"aaaa",就用""號創建一個String,直接放到Pool中,比如:String t = "a"+ "b" +"c"; 會優化成"abc",然後放入Pool中;又比如String s = "x"+"y"+ref;在編譯時有部分的優化:"xy",而ref + "x" +"y"就不會有部分的優化,"+"從左到右執行,ref是變量,編譯時期無法確定)

4.使用包含變量的字符串連接符如"aa" + s1創建的對象是運行期才創建的,存儲在heap中.

(根據java api文檔中String類所講,會在內部使用stringbuffer及其append方法來實現連接,然後執行toString(),這樣就會在運行時又創建一個新對象)

還有幾個經常考的面試題:

1.
String s1 = new String("s1") ;
String s2 = new String("s1") ;
上面創建了幾個String對象?
答案:3個 ,編譯期Constant Pool中創建1個,運行期heap中創建2個.

發現有空還要看看基礎了.+.jvm

沒有留言:

張貼留言