天天看點

String 類源碼解讀定義屬性内部類構造方法其他方法

String 雖然不是幾大基礎資料類型之一,但它也是我們很常見的資料類型。最常見聽到最多的就是 String 是不可變的。

定義

String 類的定義語句如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
           

可以了解到以下幾點:

  1. 被 final 關鍵字修飾過,是以該類不可以被繼承。
  2. 該類實作了 CharSequence 接口(可以看 CharSequence 接口解讀),并實作了 Comparable 接口用于對兩個執行個體化對象比較大小。java.io.Serializable 這個序列化接口,僅用于辨別序列化,後續分析暫時是不考慮這個接口。

屬性

private final char value[];
private int hash; // Default to 0
private static final long serialVersionUID = -6849794470754667710L;
public static final Comparator<String> CASE_INSENSITIVE_ORDER
        = new CaseInsensitiveComparator();
           
  1. 在 String 内部,是利用 value[] 這個 char 類型數組來存儲 String 内容的,并且這個數組是被 final 修飾的,表示這個值初始化後是無法改變的。
  2. 而 hash 它是 String 執行個體化 hashcode 的緩存,因為我們知道 String 經常拿來被比較,而比較就需要進行 hashcode 值得比較,如果每次比較都重新計算一次 hashcode 的值就很麻煩,是以緩存下來是比較好的操作。
  3. 序列值用在序列化的時候。
  4. CASE_INSENSITIVE_ORDER 其本質是持有 String 靜态内部類,用于忽略大小寫得比較兩個字元串。

内部類

String 類裡有一個靜态内部類 CaseInsensitiveComparator:

private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        // use serialVersionUID from JDK 1.2.2 for interoperability
        private static final long serialVersionUID = 8575799808933029326L;
        public int compare(String s1, String s2) {
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }
        /** Replaces the de-serialized object. */
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }
           

這個類主要實作的是 compare 方法,實作的功能也很容易看出來就是忽略字元大小寫比較兩個 String 對象。

這裡有兩個問題:

  1. 看代碼 14 - 21 行,為什麼字元轉換為大寫之後不相等還要轉換為小寫再比較一次?不是有點多餘?

    是因為 Java 為了針對 Georgian(格魯吉亞)字母表奇怪的大小寫轉換規則而專門又增加了一步判斷。

  2. 在 String 中已經有了一個 ompareTo 的方法,為什麼還要有一個 CaseInsensitiveComparator 的内部靜态類呢?

    看過一些人的解釋,感覺主要的思路在于代碼複用吧。首先不區分大小寫比較字元串是一個比較常見的需求。簡單對比的話,隻需要調用 String. compareTo(another)。如果用于不區分大小寫排序,則需要一個不區分大小寫字元的 Comparator 執行個體。而實際中 String 類中提供的 compareToIgnoreCase 方法其實就是調用這個内部類裡面的方法實作的。

    至于私有靜态内部類,是因為這個執行個體不需要執行個體化多個,它是單例的,用的時候隻需要用類的 CASE_INSENSITIVE_ORDER 屬性即可。

構造方法

String 支援多種初始化方法,包括接收 String、char[]、byte[]、StringBuffer 等多種參數類型的初始化方法。展示兩個作為樣例:

public String() {
        this.value = "".value;
    }
public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
           

我們可以看到不管傳遞什麼參數類型本質上就是将接收到的參數傳遞給全局變量 value[]。char[] 參數的構造方法調用了 Arrays.copyOf 方法來進行拷貝,也說明了 String 的不可變性。

其他方法

length()、isEmpty()、charAt()方法

因為 String 内部是 char 數組,是以這些函數的操作都是通過内部調用數組的方法來實作。

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

equals()、hashcode()、contentEquals()方法

equals() 和 hashCode() 兩個方法服務于字元串比較。

public boolean equals(Object anObject) {
     // 如果記憶體位址相同,則傳回true
        if (this == anObject) {
            return true;
        }
     // 隻有比較的對象是String的執行個體才能進行比較。否則直接false
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            // 比較各自String的字元數組的長度是否相同
            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;
    }
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;
    }
           

除 equals() 外,還有隻比較内容的 contentEquals() 方法。這個方法主要是用來比較String和StringBuffer或者StringBuild的内容是否一樣。

// 傳入參數為CharSequence,說明了String、StringBuffer和StringBuild都實作了CharSequence接口。
public boolean contentEquals(CharSequence cs) {
        // 先判斷參數是從哪一個類執行個體化來的
        // Argument is a StringBuffer, StringBuilder
        if (cs instanceof AbstractStringBuilder) {
            if (cs instanceof StringBuffer) {
                synchronized(cs) {
                   return nonSyncContentEquals((AbstractStringBuilder)cs);
                }
            } else {
                return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        }
        // Argument is a String
        if (cs instanceof String) {
            return equals(cs);
        }
        // for循環來進行判斷兩字元串是否内容相同
        char v1[] = value;
        int n = v1.length;
        if (n != cs.length()) {
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != cs.charAt(i)) {
                return false;
            }
        }
        return true;
    }
           

replace() 方法

替換單個字元。主要是這個方法最後會傳回一個新的字元串,而不是改原來的 String,是以使用的時候記得用 String 來接收。

public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */
        //當找出第一個與想替換的字元時,跳出循環。
            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
           
            if (i < len) {
       //定義一個新的字元數組來裝String的值。然後這buf[]裡面進行替換
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
        //替換的方法。逐個比較,相同則替換成新的,不同則還是c.三目運算
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
          //最後new了一個String傳回。
                return new String(buf, true);
            }
        }
        return this;
    }
           

startsWith() 方法

判斷目前字元串是否以某一段其他字元串開始的,通過一個 while 來循環比較實作。

public boolean startsWith(String prefix, int toffset) {
        char ta[] = value;
        int to = toffset;
        char pa[] = prefix.value;
        int po = 0;
        int pc = prefix.value.length;
        
        if ((toffset < 0) || (toffset > value.length - pc)) {
            return false;
        }
        while (--pc >= 0) {
            if (ta[to++] != pa[po++]) {
                return false;
            }
        }
        return true;
    }
           

indexOf() 方法

獲得某個字元在 String 的字元數組裡面的第一次出現的下标。

public int indexOf(int ch, int fromIndex) {
        final int max = value.length;
        if (fromIndex < 0) {
            fromIndex = 0;
        } else if (fromIndex >= max) {
            // Note: fromIndex might be near -1>>>1.
            return -1;
        }
    
        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            // handle most cases here (ch is a BMP code point or a
            // negative value (invalid code point))
            final char[] value = this.value;
            for (int i = fromIndex; i < max; i++) {
                if (value[i] == ch) {
                    return i;
                }
            }
            return -1;
        } else {
            return indexOfSupplementary(ch, fromIndex);
        }
    }
           

我們可以看到 indexOf 的參數是 int 而不是 char 類型。因為 char 存儲的值通常都是比 0x010000 小的,就是 BMP 類型的字元,而當比這個值大的時候,就是增補字元了,但也是有效的字元。是以在代碼的第 10 行也會看到 ch < Character.MIN_SUPPLEMENTARY_CODE_POINT 來保證這個是有效的字元。

是以這裡的 int 是目标字元的 Unicode 值,放單個字元的話也會轉成字元在 Unicode 編碼中對應的代碼值。

concat() 方法

它可以将 str 拼接到目前字元串後面。都是都底層的 arraycopy 方法來實作的。也是建一個新的字元串傳回。

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