java對(duì)String字符串對(duì)象的創(chuàng)建以及管理

字號(hào):

Constant Pool常量池的概念:
    在講到String的一些特殊情況時(shí),總會(huì)提到String Pool或者Constant Pool,但是我想很多人都不太明白Constant Pool到底是個(gè)怎么樣的東西,運(yùn)行的時(shí)候存儲(chǔ)在哪里,所以在這里先說(shuō)一下Constant Pool的內(nèi)容。
    String Pool是對(duì)應(yīng)于在Constant Pool中存儲(chǔ)String常量的區(qū)域.習(xí)慣稱為String Pool,也有人稱為String Constant Pool.好像沒(méi)有正式的命名。
    在java編譯好的class文件中,有個(gè)區(qū)域稱為Constant Pool,他是一個(gè)由數(shù)組組成的表,類(lèi)型為cp_info constant_pool[],用來(lái)存儲(chǔ)程序中使用的各種常量,包括Class/String/Integer等各種基本Java數(shù)據(jù)類(lèi)型,詳情參見(jiàn)The Java Virtual Machine Specification 4.4章節(jié)。
    對(duì)于Constant Pool,表的基本通用結(jié)構(gòu)為:
    cp_info {
    u1 tag;
    u1 info[];
    }
    tag是一個(gè)數(shù)字,用來(lái)表示存儲(chǔ)的常量的類(lèi)型,例如8表示String類(lèi)型,5表示Long類(lèi)型,info[]根據(jù)
    類(lèi)型碼tag的不同會(huì)發(fā)生相應(yīng)變化。
    對(duì)于String類(lèi)型,表的結(jié)構(gòu)為:
    CONSTANT_String_info {
    u1 tag;
    u2 string_index;
    }
    tag固定為8,string_index是字符串內(nèi)容信息,類(lèi)型為:
    CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
    }
    tag固定為1,length為字符串的長(zhǎng)度,bytes[length]為字符串的內(nèi)容。
    (以下代碼在jdk6中編譯)
    為了詳細(xì)理解Constant Pool的結(jié)構(gòu),我們參看一些代碼:
    String s1 = "sss111";
    String s2 = "sss222";
    System.out.println(s1 + " " + s2);
    由于"sss111"和"sss222"都是字符串常量,在編譯期就已經(jīng)創(chuàng)建好了存儲(chǔ)在class文件中。
    在編譯后的class文件中會(huì)存在這2個(gè)常量的對(duì)應(yīng)表示:
    08 00 11 01 00 06 73 73 73 31 31 31 08 00 13 01 ; ......sss111....
    00 06 73 73 73 32 32 32 ; ..sss222
    根據(jù)上面說(shuō)的String常量結(jié)構(gòu),我們分析一下:
    開(kāi)始的08為CONSTANT_String_info結(jié)構(gòu)中的tag,而11應(yīng)該是它的相對(duì)引用,01為CONSTANT_Utf8_info的tag,06為對(duì)應(yīng)字符串的長(zhǎng)度,73 73 73 31 31 31為字符串對(duì)應(yīng)的編碼,接著分析,會(huì)發(fā)現(xiàn)后面的是對(duì)應(yīng)"sss222"的存儲(chǔ)結(jié)構(gòu)。
    經(jīng)過(guò)上面分析,我們知道了11和13是兩個(gè)字符串的相對(duì)引用,就可以修改class文件來(lái)修改打印的內(nèi)容,把class文件中的00 6E 00 04 00 03 00 00 00 24 12 10 4C 12 12 4D改成00 6E 00 04 00 03 00 00 00 24 12 10 4C 12 10 4D,程序就會(huì)輸出sss111 sss111,而不是和原程序一樣輸出sss111 sss222,因?yàn)槲覀儼褜?duì)"sss222"的相對(duì)引用12改成了對(duì)"sss111"的相對(duì)引用10。
    public class Test {
    public static void main(String[] args) {
    String s1 = "sss111";
    String s2 = "sss111";
    }
    }
    在上面程序中存在2個(gè)相同的常量"sss111",對(duì)于n個(gè)值相同的String常量,在Constant Pool中只會(huì)創(chuàng)建一個(gè),所以在編譯好的class文件中,我們只能找到一個(gè)對(duì)"sss111"的表示:
    000000abh: 08 00 11 01 00 06 73 73 73 31 31 31 ; ......sss111
    在程序執(zhí)行的時(shí)候,Constant Pool會(huì)儲(chǔ)存在Method Area,而不是heap中。
    另外,對(duì)于""內(nèi)容為空的字符串常量,會(huì)創(chuàng)建一個(gè)長(zhǎng)度為0,內(nèi)容為空的字符串放到Constant Pool中,
    而且Constant Pool在運(yùn)行期是可以動(dòng)態(tài)擴(kuò)展的。
    關(guān)于String類(lèi)的說(shuō)明
    1.String使用private final char value[]來(lái)實(shí)現(xiàn)字符串的存儲(chǔ),也就是說(shuō)String對(duì)象創(chuàng)建之后,就不能再修改此對(duì)象中存儲(chǔ)的字符串內(nèi)容,就是因?yàn)槿绱?才說(shuō)String類(lèi)型是不可變的(immutable)。
    2.String類(lèi)有一個(gè)特殊的創(chuàng)建方法,就是使用""雙引號(hào)來(lái)創(chuàng)建.例如new String("i am")實(shí)際創(chuàng)建了2個(gè)
    String對(duì)象,一個(gè)是"i am"通過(guò)""雙引號(hào)創(chuàng)建的,另一個(gè)是通過(guò)new創(chuàng)建的.只不過(guò)他們創(chuàng)建的時(shí)期不同,
    一個(gè)是編譯期,一個(gè)是運(yùn)行期!
    3.java對(duì)String類(lèi)型重載了+操作符,可以直接使用+對(duì)兩個(gè)字符串進(jìn)行連接。
    4.運(yùn)行期調(diào)用String類(lèi)的intern()方法可以向String Pool中動(dòng)態(tài)添加對(duì)象。
    String的創(chuàng)建方法一般有如下幾種
    1.直接使用""引號(hào)創(chuàng)建;
    2.使用new String()創(chuàng)建;
    3.使用new String("someString")創(chuàng)建以及其他的一些重載構(gòu)造函數(shù)創(chuàng)建;
    4.使用重載的字符串連接操作符+創(chuàng)建。
    例1
    String s1 = "sss111";
    //此語(yǔ)句同上
    String s2 = "sss111";
    System.out.println(s1 == s2); //結(jié)果為true
    例2
    String s1 = new String("sss111");
    String s2 = "sss111";
    System.out.println(s1 == s2); //結(jié)果為false
    例3
    String s1 = new String("sss111");
    s1 = s1.intern();
    String s2 = "sss111";
    System.out.println(s1 == s2);
    例4
    String s1 = new String("111");
    String s2 = "sss111";
    String s3 = "sss" + "111";
    String s4 = "sss" + s1;
    System.out.println(s2 == s3); //true
    System.out.println(s2 == s4); //false
    System.out.println(s2 == s4.intern()); //true
    例5
    這個(gè)是The Java Language Specification中3.10.5節(jié)的例子,有了上面的說(shuō)明,這個(gè)應(yīng)該不難理解了
    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)) + " ");
    System.out.println(hello == ("Hel"+lo).intern());
    }
    }
    class Other { static String hello = "Hello"; }
    package other;
    public class Other { static String hello = "Hello"; }
    輸出結(jié)果為true true true true false true,請(qǐng)自行分析!
    結(jié)果上面分析,總結(jié)如下:
    1.單獨(dú)使用""引號(hào)創(chuàng)建的字符串都是常量,編譯期就已經(jīng)確定存儲(chǔ)到String Pool中;
    2.使用new String("")創(chuàng)建的對(duì)象會(huì)存儲(chǔ)到heap中,是運(yùn)行期新創(chuàng)建的;
    3.使用只包含常量的字符串連接符如"aa" + "aa"創(chuàng)建的也是常量,編譯期就能確定,已經(jīng)確定存儲(chǔ)到String Pool中;
    4.使用包含變量的字符串連接符如"aa" + s1創(chuàng)建的對(duì)象是運(yùn)行期才創(chuàng)建的,存儲(chǔ)在heap中;
    5.使用"aa" + s1以及new String("aa" + s1)形式創(chuàng)建的對(duì)象是否加入到String Pool中我不太確定,可能是必須調(diào)用intern()方法才會(huì)加入,希望高手能回答!
    還有幾個(gè)經(jīng)??嫉拿嬖囶}:
    1.
    String s1 = new String("s1") ;
    String s2 = new String("s1") ;
    上面創(chuàng)建了幾個(gè)String對(duì)象?
    答案:3個(gè) ,編譯期Constant Pool中創(chuàng)建1個(gè),運(yùn)行期heap中創(chuàng)建2個(gè).
    2.
    String s1 = "s1";
    String s2 = s1;
    s2 = "s2";
    s1指向的對(duì)象中的字符串是什么?
    答案: "s1"