Java入門系列之字符串特性(二)

前言

上一節咱們講解到字符串本質上就是字符數組,同時詳細講解了字符串判斷相等須要注意的地方,本節咱們來深刻探討字符串特性,下面咱們一塊兒來看看。java

不可變性

咱們依然藉助初始化字符串的方式來探討字符串的不可變性,以下:數組

String str = "Jeffcky";
System.out.println(str);

上述咱們經過字面量的方式來建立字符串,接下來咱們對字符串str進行以下操做:緩存

String str = "Jeffcky";
str.substring(0,3).concat("wang").toLowerCase().trim(); 
System.out.println(str);

咱們看到針對str字符串進行截取、鏈接、小寫等操做後,字符串的值依然未發生改變,這就是字符串的不可變性,咱們經過查看任意一個對字符串操做的方法,好比concat方法源碼:安全

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

經過查看concat源碼得出:原始的str值永遠都沒有發生改變,它的值只是被複制,而後將咱們鏈接的文本添加到複製的副本里,最後返回一個新的String。因此到這裏咱們知道針對字符串的操做都是複製一份字符串,而後對複製後的字符串進行操做,最終返回一個新的String對象。多線程

字符串池

咱們再來看看上一節所給出的代碼示例,以下:優化

public class Main {
    public static void main(String[] args) {
        String str1 = "Jeffcky";
        String str2 = "Jeffcky";
        System.out.println(str1 == str2);
        System.out.println(str1.equals(str2));
    }
}

當咱們實例化一個String時(在本例中爲Jeffcky)保存在Java堆內存(用於全部Java對象的動態內存分配)中。雖然在這個例子中咱們有兩個不一樣的引用變量,但它們都只是指Java Heap Memory中的同一內存位置,雖然看起來有兩個不一樣的String對象,但實際上只有一個,而str2永遠不會被實例化爲對象,而是在內存中分配對應於str1的對象,這是由於Java針對字符串進行了優化處理,每次要實例化此類String對象時,都會將要添加到堆內存的值與先前添加的值進行比較,若是值已存在,則不初始化對象,並將值分配給引用變量,這些值保存在名叫「字符串池」中,該字符串池包含全部文字字符串值,固然咱們能夠經過new運算符繞過這種狀況。this

爲何字符串是不可變或final呢? 

上述咱們經過例子說明了字符串的不可變性特性,那麼爲何字符串是不可變的呢?能夠參考知乎回答:《https://www.zhihu.com/question/31345592》。我認爲主要在於有效共享對象,節省內存空間。當程序運行時,建立的String實例的數量也會增加,若是不緩存String常量,堆空間中會有大量的String,佔用內存空間,因此String對象被建立後緩存在字符串池中,若緩存的字符串被多個客戶端共享,此時一個客戶端的操做修改了字符串則影響到其餘客戶端,所以經過字符串的不可變性來規避這種風險,同時經過緩存和共享字符串常量,JVM爲Java應用程序節省內存。spa

有了字符串不可變性,能夠很安全的被多線程所共享,咱們不用擔憂線程同步問題,確保線程安全。線程

有了字符串不可變性,能夠很好的使用好比HashMap,咱們能正確檢索到存儲到HashMap中的對象,若字符串可變且在插入到HashMap後並修改了字符串內容,此時將會出現丟失對應字符串所映射的對象。設計

有了字符串不可變性,此時會緩存字符串哈希碼,因此每次調用字符串的hashcode方法時都不用計算,使得在HashMap中使用鍵很是快。

其餘等等......

接下來咱們一塊兒來經過源碼的方式來看看String的實現,以下:

public class Main {
    public static void main(String[] args) {
        char a[] = {'j', 'e', 'f', 'f', 'c', 'k', 'y'};
        String str = new String(a);
        System.out.println(str);
    }
}

咱們經過字符數組建立字符串的方式去查看源碼,以下:

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

    /**
     * Initializes a newly created {@code String} object so that it represents
     * an empty character sequence.  Note that use of this constructor is
     * unnecessary since Strings are immutable.
     */
    public String() {
        this.value = new char[0];
    }

    /**
     * Allocates a new {@code String} so that it represents the sequence of
     * characters currently contained in the character array argument. The
     * contents of the character array are copied; subsequent modification of
     * the character array does not affect the newly created string.
     *
     * @param  value
     *         The initial value of the string
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    
    ...
}

咱們看到字符串對象定義爲final(當前咱們還未學到final,咱們只須要知道經過final關鍵字修飾說明該類不可繼承),網上有不少例子說字符串對象經過final關鍵字修飾,說明字符串不可變,其實這種說法是不嚴謹且錯誤的。String經過final關鍵字修飾的緣由在於:確保不能經過擴展和覆蓋行爲來破壞String類的不可變性而非說明字符串不可變。好比,以下例子:

public class Main {
    public static void main(String[] args) {
        String str1 = "Jeffcky";
        String str2 = "Jeffcky".toUpperCase();
        System.out.println(str1);
        System.out.println(str2);
    }
}

如今字符串str2爲"Jeffcky".toUpperCase(),咱們將同一個對象修改成「JEFFCKY」,若是修改了字符串變量,其餘字符串變量也將自動受到影響,好比str1也將是"JEFFCKY",很顯然是不可取的。

總結

本文咱們詳細介紹了字符串的不可變、字符串池特性,同時解釋了字符串爲什麼不可變,以及說明字符串類定義爲final,並非說明其不可變,只是爲了避免容許經過擴展或覆蓋來破壞字符串的不可變性。

相關文章
相關標籤/搜索