1. 程式人生 > >JDK6和JDK7中String的substring()方法及其差異

JDK6和JDK7中String的substring()方法及其差異

翻譯人員: 鐵錨
翻譯日期: 2013年11月2日
原文連結: The substring() Method in JDK 6 and JDK 7 
 
在JDK6與JDK7這兩個版本中,substring(int beginIndex, int endIndex)方法是不同的. 瞭解兩個版本間的區別可以讓你更好地使用它們. 為簡單起見,本文中以 substring() 表示 substring(int beginIndex, int endIndex).

1. substring()功能簡介
String物件的substring(int beginIndex, int endIndex)方法返回此物件的一個子串,從beginIndex 開始,一直到 endIndex-1 結束,共 (endIndex - beginIndex)個字元。
新手提示: 

    1.1 String 的索引和陣列一樣,都是從0開始.
    1.2 注意,方法名字是substring(),全小寫.
    1.3 有個過載方法是substring(int beginIndex),從beginIndex索引處開始,取得子字串.
String x = "abcdef";
int begin=1;
int end=3;
x = x.substring(begin, end);
System.out.println(x);

執行結果(包含索引為 begin,直到 end-1 的字元):
bc

2. 當substring()被呼叫時,發生了什麼?
你應該知道,因為 x 是不可變的,當 指定 x 等於 x.substring(begin, end)時,實際上 x 指向了一個全新的字串,如下圖所示:

圖1



然而,這幅圖並不是完全正確的,堆記憶體中所真正發生的事也不是這麼簡單.那麼,在JDK6和JDK7之間 substring()的呼叫到底有些什麼區別呢?

3. JDK 6中的substring()方法
String實際上是一個字元陣列.在 JDK6中, String物件主要包含3個屬性域: 
private final char value[];
private final int offset;
private final int count;

他們用於儲存實際的字元陣列,陣列的第一個索引,以及String的字元個數.
當呼叫 substring() 方法時,建立了一個新的String物件,但是string的value[] 屬性域仍然指向堆記憶體中的原來的那個陣列。區別就是 兩個物件的 count 和 offset 這兩個值不同了。 如下圖所示:

圖2 要解釋這個問題,下面是最關鍵部分的程式碼:
// JDK6,包級私有構造,共享 value陣列提升速度
String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;
}


public String substring(int beginIndex, int endIndex) {
    // ... 檢查邊界的程式碼
    // 如果範圍和自己一模一樣,則返回自身,否則用value字元陣列構造一個新的物件
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

4. JDK 6中substring()引起的問題
如果有一個"非常"長的字串,但每次使用substring()時只想要很小的一部分,那麼將會引起另一個性能問題: 雖然你只需要很小的一部分,但是持有了整個value[]的引用,從而導致大量記憶體被佔用。
要解決這個問題,在JDK6中可以讓其指向一個真正的子字串,示例程式碼:
x = x.substring(begin, end) + "";

5. JDK 7中的substring()方法
在JDK 7 中這個問題得到改進, substring()方法真實地在堆記憶體中建立了另一個字元陣列.

圖3
// JDK 7, 許可權變為 public 
public String(char value[], int offset, int count) {
    // ... 檢查邊界..
    // value 陣列拷貝
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}


public String substring(int beginIndex, int endIndex) {
    // ... 檢查邊界..
    int subLen = endIndex - beginIndex;
    // 如果和自身一樣,那就返回自身,否則返回構造的新物件
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
}

參考:
2. Java 6 vs Java 7 when implementation matters

相關閱讀: