瞭解String.substring的原理

1.首先我們要了解String對象放入常量池的時機

String只會在一種情況下放入常量池,那就是用""修飾時。
例如

String str1 = "abc";

這個時候會把字符串"abc"放入常量池。
又或者當編譯器可以確定變量的值時,如下:

String str1 = "abc" + "def";

這個時候會把字符串"abcdef"放入常量池。

所以我們得出一個結論substring返回值是不會保存在常量池中的,所以他會隨着GC調用而回收

驗證

    public static void main(String[] args) throws InterruptedException {
        String a = "123456";
        String b = a.substring(3);
        System.out.println(System.identityHashCode(b.intern()));
        b = null;
        System.gc();
        b = a.substring(3);
        System.out.println(System.identityHashCode(b.intern()));
        b = null;
        System.gc();
        b = a.substring(3);
        System.out.println(System.identityHashCode(b.intern()));
        // 輸出結果
        // 1804094807
        // 951007336
        // 2001049719
    }

2.substring返回時究竟發生了什麼操作

JDK1.6中的substring

JDK1.6中String對象由以下3個成員變量組成

String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;
}
  • value char數組
  • offset 偏移量
  • count 長度

所以在JDK1.6中substring本質上是生成一個新的String對象,但是引用的char數組是和父對象是同一個數組,只是改變了偏移量與長度

    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);
    }

JDK1.6中的substring中產生的問題

既然沒有數組還是同一個數組,那麼在回收大字符串的時候會因爲剪切過的字符串還在引用,而導致無法回收,從而引起內存泄漏。這個BUG也被官方收錄並在JDK1.7中得到了修正。
在這裏插入圖片描述

JDK1.7中的substring

首先JDK1.7之後,String取消了offset與count屬性。只保留了char[]。
我們來看一下JDK1.7中的substring源碼

    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);
    }
    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);
    }

在計算出偏移量之後不在引用父本的char[]數組,而是調用Arrays.copyOfRange方法生成新的數組。從而生成的新對象與原字符串不再存在聯繫,可以使的原對象得以被回收從而解決了內存泄漏問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章