有關(guān)String源碼知識(shí)點(diǎn)總結(jié)

這篇文章主要講解了“有關(guān)String源碼知識(shí)點(diǎn)總結(jié)”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“有關(guān)String源碼知識(shí)點(diǎn)總結(jié)”吧!

成都創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),平江企業(yè)網(wǎng)站建設(shè),平江品牌網(wǎng)站建設(shè),網(wǎng)站定制,平江網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,平江網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。

有關(guān)String源碼知識(shí)點(diǎn)總結(jié)

不知道大家有沒有這樣得經(jīng)歷,就是無意中點(diǎn)進(jìn)去得一個(gè)業(yè)面,然后鉆到里面瀏覽了好久,我就是這樣得,今天無意中,ctrl+左鍵,就點(diǎn)進(jìn)了string得源碼,正好今天下午沒啥事,就在里面看一下,沒想到,下次緩過來,就是我同事拍我讓我去吃飯,哈哈哈哈,不過好處就是,我這邊也整理了一些string類得知識(shí)點(diǎn),也分享給大家,整理得不好還望海涵

文章首發(fā)個(gè)人公眾號(hào):Java架構(gòu)師聯(lián)盟,每日更新技術(shù)好文

一、String類

想要了解一個(gè)類,最好的辦法就是看這個(gè)類的實(shí)現(xiàn)源代碼,來看一下String類的源碼:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    ........
}

從上面可以看出幾點(diǎn):

1)String類是final類,也即意味著String類不能被繼承,并且它的成員方法都默認(rèn)為final方法。在Java中,被final修飾的類是不允許被繼承的,并且該類中的成員方法都默認(rèn)為final方法。

2)上面列舉出了String類中所有的成員屬性,從上面可以看出String類其實(shí)是通過char數(shù)組來保存字符串的。

下面再繼續(xù)看String類的一些方法實(shí)現(xiàn):

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > count) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex > endIndex) {
        throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    }
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    char buf[] = new char[count + otherLen];
    getChars(0, count, buf, 0);
    str.getChars(0, otherLen, buf, count);
    return new String(0, count + otherLen, buf);
}

public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = count;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        int off = offset;   /* avoid getfield opcode */

        while (++i < len) {
        if (val[off + i] == oldChar) {
            break;
        }
        }
        if (i < len) {
        char buf[] = new char[len];
        for (int j = 0 ; j < i ; j++) {
            buf[j] = val[off+j];
        }
        while (i < len) {
            char c = val[off + i];
            buf[i] = (c == oldChar) ? newChar : c;
            i++;
        }
        return new String(0, len, buf);
        }
    }
    return this;
}

從上面的三個(gè)方法可以看出,無論是sub操、concat還是replace操作都不是在原有的字符串上進(jìn)行的,而是重新生成了一個(gè)新的字符串對(duì)象。也就是說進(jìn)行這些操作后,最原始的字符串并沒有被改變。

在這里要永遠(yuǎn)記住一點(diǎn):“String對(duì)象一旦被創(chuàng)建就是固定不變的了,對(duì)String對(duì)象的任何改變都不影響到原對(duì)象,相關(guān)的任何change操作都會(huì)生成新的對(duì)象”。

二、字符串常量池

我們知道字符串的分配和其他對(duì)象分配一樣,是需要消耗高昂的時(shí)間和空間的,而且字符串我們使用的非常多。JVM為了提高性能和減少內(nèi)存的開銷,在實(shí)例化字符串的時(shí)候進(jìn)行了一些優(yōu)化:使用字符串常量池。每當(dāng)我們創(chuàng)建字符串常量時(shí),JVM會(huì)首先檢查字符串常量池,如果該字符串已經(jīng)存在常量池中,那么就直接返回常量池中的實(shí)例引用。如果字符串不存在常量池中,就會(huì)實(shí)例化該字符串并且將其放到常量池中。由于String字符串的不可變性我們可以十分肯定常量池中一定不存在兩個(gè)相同的字符串(這點(diǎn)對(duì)理解上面至關(guān)重要)。

Java中的常量池,實(shí)際上分為兩種形態(tài):靜態(tài)常量池運(yùn)行時(shí)常量池。 所謂靜態(tài)常量池,即*.class文件中的常量池,class文件中的常量池不僅僅包含字符串(數(shù)字)字面量,還包含類、方法的信息,占用class文件絕大部分空間。 而運(yùn)行時(shí)常量池,則是jvm虛擬機(jī)在完成類裝載操作后,將class文件中的常量池載入到內(nèi)存中,并保存在方法區(qū)中,我們常說的常量池,就是指方法區(qū)中的運(yùn)行時(shí)常量池。

來看下面的程序:

String a = "chenssy";
String b = "chenssy";

a、b和字面上的chenssy都是指向JVM字符串常量池中的"chenssy"對(duì)象,他們指向同一個(gè)對(duì)象。

String c = new String("chenssy");

new關(guān)鍵字一定會(huì)產(chǎn)生一個(gè)對(duì)象chenssy(注意這個(gè)chenssy和上面的chenssy不同),同時(shí)這個(gè)對(duì)象是存儲(chǔ)在堆中。所以上面應(yīng)該產(chǎn)生了兩個(gè)對(duì)象:保存在棧中的c和保存堆中chenssy。但是在Java中根本就不存在兩個(gè)完全一模一樣的字符串對(duì)象。故堆中的chenssy應(yīng)該是引用字符串常量池中chenssy。所以c、chenssy、池chenssy的關(guān)系應(yīng)該是:c--->chenssy--->池chenssy。整個(gè)關(guān)系如下:

有關(guān)String源碼知識(shí)點(diǎn)總結(jié)

通過上面的圖我們可以非常清晰的認(rèn)識(shí)他們之間的關(guān)系。所以我們修改內(nèi)存中的值,他變化的是所有。

**總結(jié):**雖然a、b、c、chenssy是不同的對(duì)象,但是從String的內(nèi)部結(jié)構(gòu)我們是可以理解上面的。String c = new String("chenssy");雖然c的內(nèi)容是創(chuàng)建在堆中,但是他的內(nèi)部value還是指向JVM常量池的chenssy的value,它構(gòu)造chenssy時(shí)所用的參數(shù)依然是chenssy字符串常量。

下面再來看幾個(gè)例子:

例子1:

/**
 * 采用字面值的方式賦值
 */
public void test1(){
    String str1="aaa";
    String str2="aaa";
    System.out.println("===========test1============");
    System.out.println(str1==str2);//true 可以看出str1跟str2是指向同一個(gè)對(duì)象 
}

執(zhí)行上述代碼,結(jié)果為:true。 分析:當(dāng)執(zhí)行String str1="aaa"時(shí),JVM首先會(huì)去字符串池中查找是否存在"aaa"這個(gè)對(duì)象,如果不存在,則在字符串池中創(chuàng)建"aaa"這個(gè)對(duì)象,然后將池中"aaa"這個(gè)對(duì)象的引用地址返回給字符串常量str1,這樣str1會(huì)指向池中"aaa"這個(gè)字符串對(duì)象;如果存在,則不創(chuàng)建任何對(duì)象,直接將池中"aaa"這個(gè)對(duì)象的地址返回,賦給字符串常量。當(dāng)創(chuàng)建字符串對(duì)象str2時(shí),字符串池中已經(jīng)存在"aaa"這個(gè)對(duì)象,直接把對(duì)象"aaa"的引用地址返回給str2,這樣str2指向了池中"aaa"這個(gè)對(duì)象,也就是說str1和str2指向了同一個(gè)對(duì)象,因此語句System.out.println(str1 == str2)輸出:true。

例子2:

/**
 * 采用new關(guān)鍵字新建一個(gè)字符串對(duì)象
 */
public void test2(){
    String str3=new String("aaa");
    String str4=new String("aaa");
    System.out.println("===========test2============");
    System.out.println(str3==str4);//false 可以看出用new的方式是生成不同的對(duì)象 
}

執(zhí)行上述代碼,結(jié)果為:false。

分析: 采用new關(guān)鍵字新建一個(gè)字符串對(duì)象時(shí),JVM首先在字符串池中查找有沒有"aaa"這個(gè)字符串對(duì)象,如果有,則不在池中再去創(chuàng)建"aaa"這個(gè)對(duì)象了,直接在堆中創(chuàng)建一個(gè)"aaa"字符串對(duì)象,然后將堆中的這個(gè)"aaa"對(duì)象的地址返回賦給引用str3,這樣,str3就指向了堆中創(chuàng)建的這個(gè)"aaa"字符串對(duì)象;如果沒有,則首先在字符串池中創(chuàng)建一個(gè)"aaa"字符串對(duì)象,然后再在堆中創(chuàng)建一個(gè)"aaa"字符串對(duì)象,然后將堆中這個(gè)"aaa"字符串對(duì)象的地址返回賦給str3引用,這樣,str3指向了堆中創(chuàng)建的這個(gè)"aaa"字符串對(duì)象。當(dāng)執(zhí)行String str4=new String("aaa")時(shí), 因?yàn)椴捎胣ew關(guān)鍵字創(chuàng)建對(duì)象時(shí),每次new出來的都是一個(gè)新的對(duì)象,也即是說引用str3和str4指向的是兩個(gè)不同的對(duì)象,因此語句System.out.println(str3 == str4)輸出:false。

例子3:

/**
 * 編譯期確定
 */
public void test3(){
    String s0="helloworld";
    String s1="helloworld";
    String s2="hello"+"world";
    System.out.println("===========test3============");
    System.out.println(s0==s1); //true 可以看出s0跟s1是指向同一個(gè)對(duì)象 
    System.out.println(s0==s2); //true 可以看出s0跟s2是指向同一個(gè)對(duì)象 
}

執(zhí)行上述代碼,結(jié)果為:true、true。

分析:因?yàn)槔又械膕0和s1中的"helloworld”都是字符串常量,它們?cè)诰幾g期就被確定了,所以s0s1為true;而"hello”和"world”也都是字符串常量,當(dāng)一個(gè)字符串由多個(gè)字符串常量連接而成時(shí),它自己肯定也是字符串常量,所以s2也同樣在編譯期就被解析為一個(gè)字符串常量,所以s2也是常量池中"helloworld”的一個(gè)引用。所以我們得出s0s1==s2。

例子4:

/**
 * 編譯期無法確定
 */
public void test4(){
    String s0="helloworld"; 
    String s1=new String("helloworld"); 
    String s2="hello" + new String("world"); 
    System.out.println("===========test4============");
    System.out.println( s0==s1 ); //false  
    System.out.println( s0==s2 ); //false 
    System.out.println( s1==s2 ); //false
}

執(zhí)行上述代碼,結(jié)果為:false、false、false。

分析:用new String() 創(chuàng)建的字符串不是常量,不能在編譯期就確定,所以new String() 創(chuàng)建的字符串不放入常量池中,它們有自己的地址空間。

s0還是常量池中"helloworld”的引用,s1因?yàn)闊o法在編譯期確定,所以是運(yùn)行時(shí)創(chuàng)建的新對(duì)象"helloworld”的引用,s2因?yàn)橛泻蟀氩糠謓ew String(”world”)所以也無法在編譯期確定,所以也是一個(gè)新創(chuàng)建對(duì)象"helloworld”的引用。

例子5:

/**
 * 繼續(xù)-編譯期無法確定
 */
public void test5(){
    String str1="abc";   
    String str2="def";   
    String str3=str1+str2;
    System.out.println("===========test5============");
    System.out.println(str3=="abcdef"); //false
}

執(zhí)行上述代碼,結(jié)果為:false。

分析:因?yàn)閟tr3指向堆中的"abcdef"對(duì)象,而"abcdef"是字符串池中的對(duì)象,所以結(jié)果為false。JVM對(duì)String str="abc"對(duì)象放在常量池中是在編譯時(shí)做的,而String str3=str1+str2是在運(yùn)行時(shí)刻才能知道的。new對(duì)象也是在運(yùn)行時(shí)才做的。而這段代碼總共創(chuàng)建了5個(gè)對(duì)象,字符串池中兩個(gè)、堆中三個(gè)。+運(yùn)算符會(huì)在堆中建立來兩個(gè)String對(duì)象,這兩個(gè)對(duì)象的值分別是"abc"和"def",也就是說從字符串池中復(fù)制這兩個(gè)值,然后在堆中創(chuàng)建兩個(gè)對(duì)象,然后再建立對(duì)象str3,然后將"abcdef"的堆地址賦給str3。

步驟: 1)棧中開辟一塊中間存放引用str1,str1指向池中String常量"abc"。 2)棧中開辟一塊中間存放引用str2,str2指向池中String常量"def"。 3)棧中開辟一塊中間存放引用str3。 4)str1 + str2通過StringBuilder的最后一步toString()方法還原一個(gè)新的String對(duì)象"abcdef",因此堆中開辟一塊空間存放此對(duì)象。 5)引用str3指向堆中(str1 + str2)所還原的新String對(duì)象。 6)str3指向的對(duì)象在堆中,而常量"abcdef"在池中,輸出為false。

例子6:

/**
 * 編譯期優(yōu)化
 */
public void test6(){
    String s0 = "a1"; 
    String s1 = "a" + 1; 
    System.out.println("===========test6============");
    System.out.println((s0 == s1)); //result = true  
    String s2 = "atrue"; 
    String s3= "a" + "true"; 
    System.out.println((s2 == s3)); //result = true  
    String s4 = "a3.4"; 
    String s5 = "a" + 3.4; 
    System.out.println((s4 == s5)); //result = true
}

執(zhí)行上述代碼,結(jié)果為:true、true、true。

分析:在程序編譯期,JVM就將常量字符串的"+"連接優(yōu)化為連接后的值,拿"a" + 1來說,經(jīng)編譯器優(yōu)化后在class中就已經(jīng)是a1。在編譯期其字符串常量的值就確定下來,故上面程序最終的結(jié)果都為true。

/**
 * 編譯期無法確定
 */
public void test7(){
    String s0 = "ab"; 
    String s1 = "b"; 
    String s2 = "a" + s1; 
    System.out.println("===========test7============");
    System.out.println((s0 == s2)); //result = false
}

執(zhí)行上述代碼,結(jié)果為:false。

分析:JVM對(duì)于字符串引用,由于在字符串的"+"連接中,有字符串引用存在,而引用的值在程序編譯期是無法確定的,即"a" + s1無法被編譯器優(yōu)化,只有在程序運(yùn)行期來動(dòng)態(tài)分配并將連接后的新地址賦給s2。所以上面程序的結(jié)果也就為false。

例子8:

/**
 * 比較字符串常量的“+”和字符串引用的“+”的區(qū)別
 */
public void test8(){
    String test="javalanguagespecification";
    String str="java";
    String str1="language";
    String str2="specification";
    System.out.println("===========test8============");
    System.out.println(test == "java" + "language" + "specification");
    System.out.println(test == str + str1 + str2);
}

執(zhí)行上述代碼,結(jié)果為:true、false。

分析:為什么出現(xiàn)上面的結(jié)果呢?這是因?yàn)?,字符串字面量拼接操作是在Java編譯器編譯期間就執(zhí)行了,也就是說編譯器編譯時(shí),直接把"java"、"language"和"specification"這三個(gè)字面量進(jìn)行"+"操作得到一個(gè)"javalanguagespecification" 常量,并且直接將這個(gè)常量放入字符串池中,這樣做實(shí)際上是一種優(yōu)化,將3個(gè)字面量合成一個(gè),避免了創(chuàng)建多余的字符串對(duì)象。而字符串引用的"+"運(yùn)算是在Java運(yùn)行期間執(zhí)行的,即str + str2 + str3在程序執(zhí)行期間才會(huì)進(jìn)行計(jì)算,它會(huì)在堆內(nèi)存中重新創(chuàng)建一個(gè)拼接后的字符串對(duì)象??偨Y(jié)來說就是:字面量"+"拼接是在編譯期間進(jìn)行的,拼接后的字符串存放在字符串池中;而字符串引用的"+"拼接運(yùn)算實(shí)在運(yùn)行時(shí)進(jìn)行的,新創(chuàng)建的字符串存放在堆中。

對(duì)于直接相加字符串,效率很高,因?yàn)樵诰幾g器便確定了它的值,也就是說形如"I"+"love"+"java"; 的字符串相加,在編譯期間便被優(yōu)化成了"Ilovejava"。對(duì)于間接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因?yàn)樵诰幾g器不會(huì)對(duì)引用變量進(jìn)行優(yōu)化。

例子9:

/**
 * 編譯期確定
 */
public void test9(){
    String s0 = "ab"; 
    final String s1 = "b"; 
    String s2 = "a" + s1;  
    System.out.println("===========test9============");
    System.out.println((s0 == s2)); //result = true
}

執(zhí)行上述代碼,結(jié)果為:true。

分析:和例子7中唯一不同的是s1字符串加了final修飾,對(duì)于final修飾的變量,它在編譯時(shí)被解析為常量值的一個(gè)本地拷貝存儲(chǔ)到自己的常量池中或嵌入到它的字節(jié)碼流中。所以此時(shí)的"a" + s1和"a" + "b"效果是一樣的。故上面程序的結(jié)果為true。

例子10:

/**
 * 編譯期無法確定
 */
public void test10(){
    String s0 = "ab"; 
    final String s1 = getS1(); 
    String s2 = "a" + s1; 
    System.out.println("===========test10============");
    System.out.println((s0 == s2)); //result = false 
    
}

private static String getS1() {  
    return "b";   
}

執(zhí)行上述代碼,結(jié)果為:false。

分析:這里面雖然將s1用final修飾了,但是由于其賦值是通過方法調(diào)用返回的,那么它的值只能在運(yùn)行期間確定,因此s0和s2指向的不是同一個(gè)對(duì)象,故上面程序的結(jié)果為false。

三、總結(jié)

1.String類初始化后是不可變的(immutable)

String使用private final char value[]來實(shí)現(xiàn)字符串的存儲(chǔ),也就是說String對(duì)象創(chuàng)建之后,就不能再修改此對(duì)象中存儲(chǔ)的字符串內(nèi)容,就是因?yàn)槿绱耍耪fString類型是不可變的(immutable)。程序員不能對(duì)已有的不可變對(duì)象進(jìn)行修改。我們自己也可以創(chuàng)建不可變對(duì)象,只要在接口中不提供修改數(shù)據(jù)的方法就可以。 然而,String類對(duì)象確實(shí)有編輯字符串的功能,比如replace()。這些編輯功能是通過創(chuàng)建一個(gè)新的對(duì)象來實(shí)現(xiàn)的,而不是對(duì)原有對(duì)象進(jìn)行修改。比如:

s = s.replace("World", "Universe");

上面對(duì)s.replace()的調(diào)用將創(chuàng)建一個(gè)新的字符串"Hello Universe!",并返回該對(duì)象的引用。通過賦值,引用s將指向該新的字符串。如果沒有其他引用指向原有字符串"Hello World!",原字符串對(duì)象將被垃圾回收。

有關(guān)String源碼知識(shí)點(diǎn)總結(jié)

2.引用變量與對(duì)象

A aa; 這個(gè)語句聲明一個(gè)類A的引用變量aa[我們常常稱之為句柄],而對(duì)象一般通過new創(chuàng)建。所以aa僅僅是一個(gè)引用變量,它不是對(duì)象。

3.創(chuàng)建字符串的方式

創(chuàng)建字符串的方式歸納起來有兩類:

(1)使用""引號(hào)創(chuàng)建字符串;

(2)使用new關(guān)鍵字創(chuà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)建的;

new創(chuàng)建字符串時(shí)首先查看池中是否有相同值的字符串,如果有,則拷貝一份到堆中,然后返回堆中的地址;如果池中沒有,則在堆中創(chuàng)建一份,然后返回堆中的地址(注意,此時(shí)不需要從堆中復(fù)制到池中,否則,將使得堆中的字符串永遠(yuǎn)是池中的子集,導(dǎo)致浪費(fèi)池的空間)!

(3)使用只包含常量的字符串連接符如"aa" + "aa"創(chuàng)建的也是常量,編譯期就能確定,已經(jīng)確定存儲(chǔ)到String Pool中;

(4)使用包含變量的字符串連接符如"aa" + s1創(chuàng)建的對(duì)象是運(yùn)行期才創(chuàng)建的,存儲(chǔ)在heap中;

4.使用String不一定創(chuàng)建對(duì)象

在執(zhí)行到雙引號(hào)包含字符串的語句時(shí),如String a = "123",JVM會(huì)先到常量池里查找,如果有的話返回常量池里的這個(gè)實(shí)例的引用,否則的話創(chuàng)建一個(gè)新實(shí)例并置入常量池里。所以,當(dāng)我們?cè)谑褂弥T如String str = "abc";的格式定義對(duì)象時(shí),總是想當(dāng)然地認(rèn)為,創(chuàng)建了String類的對(duì)象str。**擔(dān)心陷阱!對(duì)象可能并沒有被創(chuàng)建!而可能只是指向一個(gè)先前已經(jīng)創(chuàng)建的對(duì)象。**只有通過new()方法才能保證每次都創(chuàng)建一個(gè)新的對(duì)象。

5.使用new String,一定創(chuàng)建對(duì)象

在執(zhí)行String a = new String("123")的時(shí)候,首先走常量池的路線取到一個(gè)實(shí)例的引用,然后在堆上創(chuàng)建一個(gè)新的String實(shí)例,走以下構(gòu)造函數(shù)給value屬性賦值,然后把實(shí)例引用賦值給a:

public String(String original) {
    int size = original.count;
    char[] originalValue = original.value;
    char[] v;
      if (originalValue.length > size) {
         // The array representing the String is bigger than the new
         // String itself.  Perhaps this constructor is being called
         // in order to trim the baggage, so make a copy of the array.
            int off = original.offset;
            v = Arrays.copyOfRange(originalValue, off, off+size);
     } else {
         // The array representing the String is the same
         // size as the String, so no point in making a copy.
        v = originalValue;
     }
    this.offset = 0;
    this.count = size;
    this.value = v;
    }

從中我們可以看到,雖然是新創(chuàng)建了一個(gè)String的實(shí)例,但是value是等于常量池中的實(shí)例的value,即是說沒有new一個(gè)新的字符數(shù)組來存放"123"。

6.關(guān)于String.intern()

**intern方法使用:**一個(gè)初始為空的字符串池,它由類String獨(dú)自維護(hù)。當(dāng)調(diào)用 intern方法時(shí),如果池已經(jīng)包含一個(gè)等于此String對(duì)象的字符串(用equals(oject)方法確定),則返回池中的字符串。否則,將此String對(duì)象添加到池中,并返回此String對(duì)象的引用。

它遵循以下規(guī)則:對(duì)于任意兩個(gè)字符串 s 和 t,當(dāng)且僅當(dāng) s.equals(t) 為 true 時(shí),s.intern() == t.intern() 因?yàn)?true。

String.intern(); 再補(bǔ)充介紹一點(diǎn):存在于.class文件中的常量池,在運(yùn)行期間被jvm裝載,并且可以擴(kuò)充。String的intern()方法就是擴(kuò)充常量池的一個(gè)方法;當(dāng)一個(gè)String實(shí)例str調(diào)用intern()方法時(shí),java查找常量池中是否有相同unicode的字符串常量,如果有,則返回其引用,如果沒有,則在常量池中增加一個(gè)unicode等于str的字符串并返回它的引用。

/**
 * 關(guān)于String.intern()
 */
public void test11(){
    String s0 = "kvill"; 
    String s1 = new String("kvill"); 
    String s2 = new String("kvill"); 
    System.out.println("===========test11============");
    System.out.println( s0 == s1 ); //false
    System.out.println( "**********" ); 
    s1.intern(); //雖然執(zhí)行了s1.intern(),但它的返回值沒有賦給s1
    s2 = s2.intern(); //把常量池中"kvill"的引用賦給s2 
    System.out.println( s0 == s1); //flase
    System.out.println( s0 == s1.intern() ); //true//說明s1.intern()返回的是常量池中"kvill"的引用
    System.out.println( s0 == s2 ); //true
}

運(yùn)行結(jié)果:false、false、true、true。

7.關(guān)于equals和==

(1)對(duì)于==,如果作用于基本數(shù)據(jù)類型的變量(byte,short,char,int,long,float,double,boolean ),則直接比較其存儲(chǔ)的"值"是否相等;如果作用于引用類型的變量(String),則比較的是所指向的對(duì)象的地址(即是否指向同一個(gè)對(duì)象)。

(2)equals方法是基類Object中的方法,因此對(duì)于所有的繼承于Object的類都會(huì)有該方法。在Object類中,equals方法是用來比較兩個(gè)對(duì)象的引用是否相等,即是否指向同一個(gè)對(duì)象。

(3)對(duì)于equals方法,注意:equals方法不能作用于基本數(shù)據(jù)類型的變量。如果沒有對(duì)equals方法進(jìn)行重寫,則比較的是引用類型的變量所指向的對(duì)象的地址;而String類對(duì)equals方法進(jìn)行了重寫,用來比較指向的字符串對(duì)象所存儲(chǔ)的字符串是否相等。其他的一些類諸如Double,Date,Integer等,都對(duì)equals方法進(jìn)行了重寫用來比較指向的對(duì)象所存儲(chǔ)的內(nèi)容是否相等。

/**
 * 關(guān)于equals和==
 */
public void test12(){
    String s1="hello";
    String s2="hello";
    String s3=new String("hello");
    System.out.println("===========test12============");
    System.out.println( s1 == s2); //true,表示s1和s2指向同一對(duì)象,它們都指向常量池中的"hello"對(duì)象
    //flase,表示s1和s3的地址不同,即它們分別指向的是不同的對(duì)象,s1指向常量池中的地址,s3指向堆中的地址
    System.out.println( s1 == s3); 
    System.out.println( s1.equals(s3)); //true,表示s1和s3所指向?qū)ο蟮膬?nèi)容相同
}

8.String相關(guān)的+:

String中的 + 常用于字符串的連接??聪旅嬉粋€(gè)簡(jiǎn)單的例子:

/**
 * String相關(guān)的+
 */
public void test13(){
    String a = "aa";
    String b = "bb";
    String c = "xx" + "yy " + a + "zz" + "mm" + b;
    System.out.println("===========test13============");
    System.out.println(c);
}

編譯運(yùn)行后,主要字節(jié)碼部分如下:

public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 5 L0
    LDC "aa"
    ASTORE 1
   L1
    LINENUMBER 6 L1
    LDC "bb"
    ASTORE 2
   L2
    LINENUMBER 7 L2
    NEW java/lang/StringBuilder
    DUP
    LDC "xxyy "
    INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "zz"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "mm"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 3
   L3
    LINENUMBER 8 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 3
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 9 L4
    RETURN
   L5
    LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
    LOCALVARIABLE a Ljava/lang/String; L1 L5 1
    LOCALVARIABLE b Ljava/lang/String; L2 L5 2
    LOCALVARIABLE c Ljava/lang/String; L3 L5 3
    MAXSTACK = 3
    MAXLOCALS = 4
}

顯然,通過字節(jié)碼我們可以得出如下幾點(diǎn)結(jié)論: (1).String中使用 + 字符串連接符進(jìn)行字符串連接時(shí),連接操作最開始時(shí)如果都是字符串常量,編譯后將盡可能多的直接將字符串常量連接起來,形成新的字符串常量參與后續(xù)連接(通過反編譯工具jd-gui也可以方便的直接看出);

(2).接下來的字符串連接是從左向右依次進(jìn)行,對(duì)于不同的字符串,首先以最左邊的字符串為參數(shù)創(chuàng)建StringBuilder對(duì)象,然后依次對(duì)右邊進(jìn)行append操作,最后將StringBuilder對(duì)象通過toString()方法轉(zhuǎn)換成String對(duì)象(注意:中間的多個(gè)字符串常量不會(huì)自動(dòng)拼接)。

也就是說String c = "xx" + "yy " + a + "zz" + "mm" + b; 實(shí)質(zhì)上的實(shí)現(xiàn)過程是: String c = new StringBuilder("xxyy ").append(a).append("zz").append("mm").append(b).toString();

由此得出結(jié)論:當(dāng)使用+進(jìn)行多個(gè)字符串連接時(shí),實(shí)際上是產(chǎn)生了一個(gè)StringBuilder對(duì)象和一個(gè)String對(duì)象。

9.String的不可變性導(dǎo)致字符串變量使用+號(hào)的代價(jià):

String s = "a" + "b" + "c"; 
String s1  =  "a"; 
String s2  =  "b"; 
String s3  =  "c"; 
String s4  =  s1  +  s2  +  s3;

分析:變量s的創(chuàng)建等價(jià)于 String s = "abc"; 由上面例子可知編譯器進(jìn)行了優(yōu)化,這里只創(chuàng)建了一個(gè)對(duì)象。由上面的例子也可以知道s4不能在編譯期進(jìn)行優(yōu)化,其對(duì)象創(chuàng)建相當(dāng)于:

StringBuilder temp = new StringBuilder();   
temp.append(a).append(b).append(c);   
String s = temp.toString();

由上面的分析結(jié)果,可就不難推斷出String 采用連接運(yùn)算符(+)效率低下原因分析,形如這樣的代碼:

public class Test {
    public static void main(String args[]) {
        String s = null;
        for(int i = 0; i < 100; i++) {
            s += "a";
        }
    }
}

每做一次 + 就產(chǎn)生個(gè)StringBuilder對(duì)象,然后append后就扔掉。下次循環(huán)再到達(dá)時(shí)重新產(chǎn)生個(gè)StringBuilder對(duì)象,然后 append 字符串,如此循環(huán)直至結(jié)束。 如果我們直接采用 StringBuilder 對(duì)象進(jìn)行 append 的話,我們可以節(jié)省 N - 1 次創(chuàng)建和銷毀對(duì)象的時(shí)間。所以對(duì)于在循環(huán)中要進(jìn)行字符串連接的應(yīng)用,一般都是用StringBuffer或StringBulider對(duì)象來進(jìn)行append操作。

10.String、StringBuffer、StringBuilder的區(qū)別

(1)可變與不可變:String是不可變字符串對(duì)象,StringBuilder和StringBuffer是可變字符串對(duì)象(其內(nèi)部的字符數(shù)組長(zhǎng)度可變)。

(2)是否多線程安全:String中的對(duì)象是不可變的,也就可以理解為常量,顯然線程安全。StringBuffer 與 StringBuilder 中的方法和功能完全是等價(jià)的,只是StringBuffer 中的方法大都采用了synchronized 關(guān)鍵字進(jìn)行修飾,因此是線程安全的,而 StringBuilder 沒有這個(gè)修飾,可以被認(rèn)為是非線程安全的。

(3)String、StringBuilder、StringBuffer三者的執(zhí)行效率: StringBuilder > StringBuffer > String 當(dāng)然這個(gè)是相對(duì)的,不一定在所有情況下都是這樣。比如String str = "hello"+ "world"的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")要高。因此,這三個(gè)類是各有利弊,應(yīng)當(dāng)根據(jù)不同的情況來進(jìn)行選擇使用: 當(dāng)字符串相加操作或者改動(dòng)較少的情況下,建議使用 String str="hello"這種形式; 當(dāng)字符串相加操作較多的情況下,建議使用StringBuilder,如果采用了多線程,則使用StringBuffer。

11.String中的final用法和理解

final StringBuffer a = new StringBuffer("111");
final StringBuffer b = new StringBuffer("222");
a=b;//此句編譯不通過

final StringBuffer a = new StringBuffer("111");
a.append("222");//編譯通過

可見,**final只對(duì)引用的"值"(即內(nèi)存地址)有效,它迫使引用只能指向初始指向的那個(gè)對(duì)象,改變它的指向會(huì)導(dǎo)致編譯期錯(cuò)誤。**至于它所指向的對(duì)象的變化,final是不負(fù)責(zé)的。

12.關(guān)于String str = new String("abc")創(chuàng)建了多少個(gè)對(duì)象?

這個(gè)問題在很多書籍上都有說到比如《Java程序員面試寶典》,包括很多國(guó)內(nèi)大公司筆試面試題都會(huì)遇到,大部分網(wǎng)上流傳的以及一些面試書籍上都說是2個(gè)對(duì)象,這種說法是片面的。

首先必須弄清楚創(chuàng)建對(duì)象的含義,創(chuàng)建是什么時(shí)候創(chuàng)建的?這段代碼在運(yùn)行期間會(huì)創(chuàng)建2個(gè)對(duì)象么?毫無疑問不可能,用javap -c反編譯即可得到JVM執(zhí)行的字節(jié)碼內(nèi)容:

有關(guān)String源碼知識(shí)點(diǎn)總結(jié)

很顯然,new只調(diào)用了一次,也就是說只創(chuàng)建了一個(gè)對(duì)象。而這道題目讓人混淆的地方就是這里,這段代碼在運(yùn)行期間確實(shí)只創(chuàng)建了一個(gè)對(duì)象,即在堆上創(chuàng)建了"abc"對(duì)象。而為什么大家都在說是2個(gè)對(duì)象呢,這里面要澄清一個(gè)概念,該段代碼執(zhí)行過程和類的加載過程是有區(qū)別的。在類加載的過程中,確實(shí)在運(yùn)行時(shí)常量池中創(chuàng)建了一個(gè)"abc"對(duì)象,而在代碼執(zhí)行過程中確實(shí)只創(chuàng)建了一個(gè)String對(duì)象。 因此,這個(gè)問題如果換成 String str = new String("abc")涉及到幾個(gè)String對(duì)象?合理的解釋是2個(gè)。 個(gè)人覺得在面試的時(shí)候如果遇到這個(gè)問題,可以向面試官詢問清楚”是這段代碼執(zhí)行過程中創(chuàng)建了多少個(gè)對(duì)象還是涉及到多少個(gè)對(duì)象“再根據(jù)具體的來進(jìn)行回答。

13.字符串池的優(yōu)缺點(diǎn):字符串池的優(yōu)點(diǎn)就是避免了相同內(nèi)容的字符串的創(chuàng)建,節(jié)省了內(nèi)存,省去了創(chuàng)建相同字符串的時(shí)間,同時(shí)提升了性能;另一方面,字符串池的缺點(diǎn)就是犧牲了JVM在常量池中遍歷對(duì)象所需要的時(shí)間,不過其時(shí)間成本相比而言比較低。

四、綜合實(shí)例

package com.spring.test;

public class StringTest {
    public static void main(String[] args) {  
        /** 
         * 情景一:字符串池 
          * JAVA虛擬機(jī)(JVM)中存在著一個(gè)字符串池,其中保存著很多String對(duì)象; 
         * 并且可以被共享使用,因此它提高了效率。 
         * 由于String類是final的,它的值一經(jīng)創(chuàng)建就不可改變。 
         * 字符串池由String類維護(hù),我們可以調(diào)用intern()方法來訪問字符串池。  
         */  
        String s1 = "abc";     
        //↑ 在字符串池創(chuàng)建了一個(gè)對(duì)象  
        String s2 = "abc";     
        //↑ 字符串pool已經(jīng)存在對(duì)象“abc”(共享),所以創(chuàng)建0個(gè)對(duì)象,累計(jì)創(chuàng)建一個(gè)對(duì)象  
        System.out.println("s1 == s2 : "+(s1==s2));    
        //↑ true 指向同一個(gè)對(duì)象,  
        System.out.println("s1.equals(s2) : " + (s1.equals(s2)));    
        //↑ true  值相等  
        //↑------------------------------------------------------over  
        /** 
         * 情景二:關(guān)于new String("") 
         *  
         */  
        String s3 = new String("abc");  
        //↑ 創(chuàng)建了兩個(gè)對(duì)象,一個(gè)存放在字符串池中,一個(gè)存在與堆區(qū)中;  
        //↑ 還有一個(gè)對(duì)象引用s3存放在棧中  
        String s4 = new String("abc");  
        //↑ 字符串池中已經(jīng)存在“abc”對(duì)象,所以只在堆中創(chuàng)建了一個(gè)對(duì)象  
        System.out.println("s3 == s4 : "+(s3==s4));  
        //↑false   s3和s4棧區(qū)的地址不同,指向堆區(qū)的不同地址;  
        System.out.println("s3.equals(s4) : "+(s3.equals(s4)));  
        //↑true  s3和s4的值相同  
        System.out.println("s1 == s3 : "+(s1==s3));  
        //↑false 存放的地區(qū)多不同,一個(gè)棧區(qū),一個(gè)堆區(qū)  
        System.out.println("s1.equals(s3) : "+(s1.equals(s3)));  
        //↑true  值相同  
        //↑------------------------------------------------------over  
        /** 
         * 情景三:  
         * 由于常量的值在編譯的時(shí)候就被確定(優(yōu)化)了。 
         * 在這里,"ab"和"cd"都是常量,因此變量str3的值在編譯時(shí)就可以確定。 
         * 這行代碼編譯后的效果等同于: String str3 = "abcd"; 
         */  
        String str1 = "ab" + "cd";  //1個(gè)對(duì)象  
        String str11 = "abcd";   
        System.out.println("str1 = str11 : "+ (str1 == str11));  
        //↑------------------------------------------------------over  
        /** 
         * 情景四:  
         * 局部變量str2,str3存儲(chǔ)的是存儲(chǔ)兩個(gè)拘留字符串對(duì)象(intern字符串對(duì)象)的地址。 
         *  
         * 第三行代碼原理(str2+str3): 
         * 運(yùn)行期JVM首先會(huì)在堆中創(chuàng)建一個(gè)StringBuilder類, 
         * 同時(shí)用str2指向的拘留字符串對(duì)象完成初始化, 
         * 然后調(diào)用append方法完成對(duì)str3所指向的拘留字符串的合并, 
         * 接著調(diào)用StringBuilder的toString()方法在堆中創(chuàng)建一個(gè)String對(duì)象, 
         * 最后將剛生成的String對(duì)象的堆地址存放在局部變量str3中。 
         *  
         * 而str5存儲(chǔ)的是字符串池中"abcd"所對(duì)應(yīng)的拘留字符串對(duì)象的地址。 
         * str4與str5地址當(dāng)然不一樣了。 
         *  
         * 內(nèi)存中實(shí)際上有五個(gè)字符串對(duì)象: 
         *       三個(gè)拘留字符串對(duì)象、一個(gè)String對(duì)象和一個(gè)StringBuilder對(duì)象。 
         */  
        String str2 = "ab";  //1個(gè)對(duì)象  
        String str3 = "cd";  //1個(gè)對(duì)象                                         
        String str4 = str2+str3;                                        
        String str5 = "abcd";    
        System.out.println("str4 = str5 : " + (str4==str5)); // false  
        //↑------------------------------------------------------over  
        /** 
         * 情景五: 
         *  JAVA編譯器對(duì)string + 基本類型/常量 是當(dāng)成常量表達(dá)式直接求值來優(yōu)化的。 
         *  運(yùn)行期的兩個(gè)string相加,會(huì)產(chǎn)生新的對(duì)象的,存儲(chǔ)在堆(heap)中 
         */  
        String str6 = "b";  
        String str7 = "a" + str6;  
        String str67 = "ab";  
        System.out.println("str7 = str67 : "+ (str7 == str67));  
        //↑str6為變量,在運(yùn)行期才會(huì)被解析。  
        final String str8 = "b";  
        String str9 = "a" + str8;  
        String str89 = "ab";  
        System.out.println("str9 = str89 : "+ (str9 == str89));  
        //↑str8為常量變量,編譯期會(huì)被優(yōu)化  
        //↑------------------------------------------------------over  
    }
}

運(yùn)行結(jié)果:

s1 == s2 : true s1.equals(s2) : true s3 == s4 : false s3.equals(s4) : true s1 == s3 : false s1.equals(s3) : true str1 = str11 : true str4 = str5 : false str7 = str67 : false str9 = str89 : true

感謝各位的閱讀,以上就是“有關(guān)String源碼知識(shí)點(diǎn)總結(jié)”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)有關(guān)String源碼知識(shí)點(diǎn)總結(jié)這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

文章題目:有關(guān)String源碼知識(shí)點(diǎn)總結(jié)
分享URL:http://bm7419.com/article34/psdese.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、網(wǎng)站維護(hù)自適應(yīng)網(wǎng)站、商城網(wǎng)站、微信小程序、網(wǎng)頁設(shè)計(jì)公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

搜索引擎優(yōu)化