【Java 學(xué)習(xí)筆記】從源碼了解字符串

之前寫過一篇關(guān)于 String 類、StringBuilder 和 StringBuffer 的基本介紹,今天從 String 類的部分源碼來看 String 類(本文基于 JDK 1.8)

  1. String 類的實(shí)例化
  2. 常用方法
  3. String 類的不可變
  4. 總結(jié)

1. String 類的構(gòu)造器

先看看 String 類中定義的一個常量和一個變量:

    /** The value is used for character storage. */
    //存儲字符
    private final char value[];

    /** Cache the hash code for the string */
    //字符串的哈希值
    private int hash; // Default to 0

翻了以下源碼,發(fā)現(xiàn)從 100 - 600 行左右都是寫的構(gòu)造器,數(shù)不勝數(shù),這里就選幾個常見的吧:

先來說說連開發(fā)者都覺得沒用的構(gòu)造器吧:

    //初始化一個 String 對象,它代表了一個空的字符序列
    //我們平常直接就 String str = "";就好了
    public String() {
        this.value = "".value;
    }

    //初始化一個和參數(shù)一模一樣的對象,除非你是要保留副本,不然沒什么用處
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

然后是根據(jù)字符數(shù)組來進(jìn)行初始化的構(gòu)造器:

    /**
     * 用一個字符數(shù)組當(dāng)前所儲存的字符來初始化字符串,
     * 后續(xù)對數(shù)組的修改將不會對字符串有影響,感覺在做
     * String 類的題目時,較常用到這個方法
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }


    //截取字符數(shù)組中的某一部分來初始化
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

最后是根據(jù) StringBuffer 和 StringBuilder 來進(jìn)行初始化的構(gòu)造器:

    //StringBuffer 是線程安全的,所以需要加鎖
    public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }

    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }

這里還有個很有意思的包私有構(gòu)造器(看不懂):

    /*
    * Package private constructor which shares value array for speed.
    * this constructor is always expected to be called with share==true.
    * a separate constructor is needed because we already have a public
    * String(char[]) constructor that makes a copy of the given char[].
    */
    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }

后來去 Google 了一下有關(guān)字符串共享的相關(guān)內(nèi)容,還是不太明白,可能是因?yàn)槌A砍氐脑?,在常量池中存放著字符串的引用,就會?dǎo)致字符串被共享,這個構(gòu)造器可能就是共享時的構(gòu)造吧(待確定),文末給出寫文時查詢這一部分內(nèi)容的鏈接,這一部分內(nèi)容等下一篇講述常量池時一起說說

2. 常用方法

1. 基本的

常用的 length(), charAt(), isEmpty() 等等都是根據(jù)根據(jù)字符數(shù)組來做操作的,就看其中一個吧:

    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

2. hashCode()

這個方法是重寫了 Object 類的 hashCode:

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

在 哈希值等于 0 且字符串不為空時,才計算哈希值,否則就返回 0,計算方法就是 for 循環(huán)里的,舉個簡單的例子算一下:

字符串 ab 的哈希值就是 97 * 32 + 98 得出的

通過對字符串的哈希值的比較,能夠得出兩個字符串的值是否相同,但是卻不能用來比較兩個對象的引用

3. join()

這個方法以前還都沒聽過,這次剛好看見了,就說一下用法:

    //第一個參數(shù)時分隔符,第二個是可變參數(shù)
    public static String join(CharSequence delimiter, CharSequence... elements) {
        Objects.requireNonNull(delimiter);
        Objects.requireNonNull(elements);
        // Number of elements not likely worth Arrays.stream overhead.
        StringJoiner joiner = new StringJoiner(delimiter);
        for (CharSequence cs: elements) {
            joiner.add(cs);
        }
        return joiner.toString();
    }

這里引出另一個類:StringJoiner,這里不介紹,如果只有一個字符串,就不會添加分隔符,若有多個,則在每兩個拼接的字符串中添加分隔符:

如果要拼接多個字符,并且要添加分隔符,用 join() 會方便很多

4. equals()

== 和 equals() 的區(qū)別相信都應(yīng)該知道了,前者比較引用(引用相同,值自然就相同),后者只比較值:

    public boolean equals(Object anObject) {
        //如果引用相同,就直接返回 true
        if (this == anObject) {
            return true;
        }
        //逐個比較字符
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

3. String 類的不可變

一直都在說 String 類是不可變的,究竟是怎么實(shí)現(xiàn)呢?

首先,String 類本身被聲明為 final:public final class String,不能被繼承,所以不可能重寫 String 類的方法(只讀),整個 String 類的核心:private final char value[] 也被聲明為 final

其次,在源碼中的返回值為 String 類型的方法,如果方法中修改了字符串的結(jié)構(gòu),在最后都是 return new String(xxx) 這樣子,如果沒有修改字符串結(jié)構(gòu),就返回這個字符串,也就是所說的:任何修改字符串結(jié)構(gòu)的操作都會產(chǎn)生一個新的 String 對象

4. 總結(jié)

其實(shí)對于 String 類,最重要的點(diǎn)就是不可變,至于其它的一些操作,感興趣的可以去翻一下源碼(3100 行,不多),注釋寫的非常清楚,很多操作其實(shí)伴隨注釋看一遍就可理解),就沒有寫在這篇文章里

最近開始著手準(zhǔn)備明年的春招了,所以剛好寫博客復(fù)習(xí)一下 Java 的一些基礎(chǔ)知識,下一篇會介紹一下常量池

參考資源:

很早之前的一篇文章,JDK 版本還沒到 1.8,所以看一看就好了http://chunlong.github.io/blog/2013/05/23/something-about-string/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容