溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

分析JDK中String的存儲區(qū)與不可變性

發(fā)布時間:2021-11-03 17:43:59 來源:億速云 閱讀:151 作者:iii 欄目:編程語言

這篇文章主要介紹“分析JDK中String的存儲區(qū)與不可變性”,在日常操作中,相信很多人在分析JDK中String的存儲區(qū)與不可變性問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”分析JDK中String的存儲區(qū)與不可變性”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

1. 數(shù)據(jù)存儲區(qū)

String是一個比較特殊的類,除了new之外,還可以用字面常量來定義。為了弄清楚這二者間的區(qū)別,首先我們得明白JVM運行時數(shù)據(jù)存儲區(qū),這里有一張圖對此有清晰的描述:
分析JDK中String的存儲區(qū)與不可變性

非共享數(shù)據(jù)存儲區(qū)

非共享數(shù)據(jù)存儲區(qū)是在線程啟動時被創(chuàng)建的,包括:

  • 程序計數(shù)器(program counter register)控制線程的執(zhí)行;

  • 棧(JVM Stack, Native Method Stack)存儲方法調(diào)用與對象的引用等。

  • 共享數(shù)據(jù)存儲區(qū)

    該存儲區(qū)被所有線程所共享,可分為:

  • 堆(Heap)存儲所有的Java對象,當執(zhí)行new對象時,會在堆里自動進行內(nèi)存分配。

  • 方法區(qū)(Method Area)存儲常量池(run-time constant pool)、字段與方法的數(shù)據(jù)、方法與構造器的代碼。

  • 2. 兩種實例化

    實例化String對象:

    public class StringLiterals {
        public static void main(String[] args) {
            String one = "Test";
            String two = "Test";
            String three = "T" + "e" + "s" + "t";
            String four = new String("Test");
        }
    }

    javap -c StringLiterals反編譯生成字節(jié)碼,我們選取感興趣的部分如下:

      public static void main(java.lang.String[]);
        Code:
           0: ldc           #2                  // String Test
           2: astore_1
           3: ldc           #2                  // String Test
           5: astore_2
           6: ldc           #2                  // String Test
           8: astore_3
           9: new           #3                  // class java/lang/String
          12: dup
          13: ldc           #2                  // String Test
          15: invokespecial #4                  // Method java/lang/String."": (Ljava/lang/String;)V
          18: astore        4
          20: return
    }

    ldc #2表示從常量池中取#2的常量入棧,astore_1表示將引用存在本地變量1中。因此,我們可以看出:對象one、two、three均指向常量池中的字面常量"Test";對象four是在堆中new的新對象;如下圖所示:
    分析JDK中String的存儲區(qū)與不可變性
    總結如下:

  • 當用字面常量實例化時,String對象存儲在常量池;

  • 當用new實例化時,String對象存儲在堆中;

  • 操作符==比較的是對象的引用,當其指向的對象不同時,則為false。因此,開篇中的代碼會出現(xiàn)通過new所創(chuàng)建String對象不一樣。

    3. 不可變String

    String源碼

    JDK7的String類:

    public final class String
        implements java.io.Serializable, Comparable, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
        /** Cache the hash code for the string */
        private int hash; // Default to 0
    }

    String類被聲明為final,不可以被繼承,所有的方法隱式地指定為final,因為無法被覆蓋。字段char value[]表示String類所對應的字符串,被聲明為private final;即初始化后不能被修改。常用的new實例化對象String s1 = new String("abcd");的構造器:

    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

    只需將value與hash的字段值進行傳遞即可。

    不可變性

    所謂不可變性(immutability)指類不可以通過常用的API被修改。為了更好地理解不可變性,我們先來看《Thinking in Java》中的一段代碼:

    //: operators/Assignment.java
    // Assignment with objects is a bit tricky.
    import static net.mindview.util.Print.*;
    class Tank {
      int level;
    }   
    public class Assignment {
      public static void main(String[] args) {
        Tank t1 = new Tank();
        Tank t2 = new Tank();
        t1.level = 9;
        t2.level = 47;
        print("1: t1.level: " + t1.level +
              ", t2.level: " + t2.level);
        t1 = t2;
        print("2: t1.level: " + t1.level +
              ", t2.level: " + t2.level);
        t1.level = 27;
        print("3: t1.level: " + t1.level +
              ", t2.level: " + t2.level);
      }
    } /* Output:
    1: t1.level: 9, t2.level: 47
    2: t1.level: 47, t2.level: 47
    3: t1.level: 27, t2.level: 27
    *///:~

    上述代碼中,在賦值操作t1 = t2;之后,t1、t2包含的是相同的引用,指向同一個對象。因此對t1對象的修改,直接影響了t2對象的字段改變。顯然,Tank類是可變的。

    也許,有人會說s = s.concat("ef");不是修改了對象s么?而事實上,我們?nèi)タ碿oncat的實現(xiàn),會發(fā)現(xiàn)其返回的是新String對象(return new String(buf, true););改變的只是s1引用所指向的對象,如下圖所示:
    分析JDK中String的存儲區(qū)與不可變性

    4. 反射

    String的value字段是final的,可不可以通過過某種方式修改呢?答案是反射。在stackoverflow上有這樣一段修改value字段的代碼:

    String s1 = "Hello World";  
    String s2 = "Hello World";  
    String s3 = s1.substring(6);  
    System.out.println(s1); // Hello World  
    System.out.println(s2); // Hello World  
    System.out.println(s3); // World  
    Field field = String.class.getDeclaredField("value");  
    field.setAccessible(true);  
    char[] value = (char[])field.get(s1);  
    value[6] = 'J';  
    value[7] = 'a';  
    value[8] = 'v';  
    value[9] = 'a';  
    value[10] = '!';  
    System.out.println(s1); // Hello Java!  
    System.out.println(s2); // Hello Java!  
    System.out.println(s3); // World

    在上述代碼中,為什么對象s2的值也會被修改,而對象s3的值卻不會呢?根據(jù)前面的介紹,s1與s2指向同一個對象;所以當s1被修改后,s2也會對應地被修改。至于s3對象為什么不會?我們來看看substring()的實現(xiàn):

    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

    當beginIndex不為0時,返回的是new的String對象;當beginIndex為0時,返回的是原對象本身。如果將String s3 = s1.substring(6);改為String s3 = s1.substring(0);,那么對象s3也會被修改了。

    如果仔細看java.lang.String.java,我們會發(fā)現(xiàn):當需要改變字符串內(nèi)容時,String類的方法返回的是新String對象;如果沒有改變,String類的方法則返回原對象引用。這節(jié)省了存儲空間與額外的開銷。

到此,關于“分析JDK中String的存儲區(qū)與不可變性”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI