從JDK原始碼看String(上)

超人汪小建發表於2018-08-06

概況

Java 語言使用 String 類用來代表字串,實際上 String 物件的值是一個常量,一旦建立後不能被改變。正式因為其不可變,所以它是執行緒安全地,可以多個執行緒共享。

相信對於 String 的使用大家都再熟悉不過的了,這裡就瞭解下 JDK 中怎麼實現 String 類的。

繼承結構

--java.lang.Object
  --java.lang.String
複製程式碼

類定義

public final class String implements java.io.Serializable, Comparable<String>, CharSequence
複製程式碼

String 類被宣告為 final,說明它不能再被繼承。同時它實現了三個介面,分別為 Serializable、Comparable 和 CharSequence。其中 Serializable 介面表明其可以序列化;

InputStream 被定為 public 且 abstract 的類,實現了Closeable介面。

Closeable 介面表示 InputStream 可以被close,介面定義如下:

public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}
複製程式碼

主要屬性

private final byte[] value;
private final byte coder;
private int hash; 

static final boolean COMPACT_STRINGS;
static {
    COMPACT_STRINGS = true;
}

static final byte LATIN1 = 0;
static final byte UTF16  = 1;

public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
複製程式碼
  • value 用於儲存字串物件的值。
  • coder 表示字串物件所用的編碼器,為LATIN1UTF16
  • hash 為字串物件的雜湊值,預設值為0。
  • COMPACT_STRINGS 表示是否使用緊湊的字元陣列佈局,預設使用。
  • CASE_INSENSITIVE_ORDER 表示用於排序的比較器

內部類

該內部類主要是提供排序的比較器,實現了Comparator介面和compare方法,另外一個readResolve方法用於替換反序列化時的物件。compare核心方法的邏輯是,根據兩者編碼是否相同做處理,如果相同則分 Latin1 或 UTF16 兩種情況比較,類似地,如果兩者編碼不同,則需要用 Latin1 編碼與 UTF16 編碼比較,而 UTF16 則要與 Latin1 比較。

private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        private static final long serialVersionUID = 8575799808933029326L;

        public int compare(String s1, String s2) {
            byte v1[] = s1.value;
            byte v2[] = s2.value;
            if (s1.coder() == s2.coder()) {
                return s1.isLatin1() ? StringLatin1.compareToCI(v1, v2)
                                     : StringUTF16.compareToCI(v1, v2);
            }
            return s1.isLatin1() ? StringLatin1.compareToCI_UTF16(v1, v2)
                                 : StringUTF16.compareToCI_Latin1(v1, v2);
        }
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }
複製程式碼

構造方法

有很多種構造方法,看主要的幾個。沒有引數的構造方法直接將空字串的 value 和 coder 進行賦值。

public String() {
    this.value = "".value;
    this.coder = "".coder;
}
複製程式碼

類似的,傳入 String 物件的構造方法則將該物件對應的 value 、coder 和 hash 進行賦值。

public String(String original) {
    this.value = original.value;
    this.coder = original.coder;
    this.hash = original.hash;
}
複製程式碼

構造方法傳入 char 陣列時,主要邏輯就是如果 COMPACT_STRINGS 為 true,即使用緊湊佈局的話,則嘗試將其轉換成為 LATIN1 編碼(即ISO-8859-1編碼),這裡說嘗試是因為 char 陣列中可能包含了非 LATIN1 編碼,此時是壓縮失敗的,只有陣列中全部都為 LATIN1 編碼時才能壓縮成功。類似的還有傳入 int 陣列的,int 型別佔用4個位元組,只有全部符合轉換才能轉成 LATIN1 編碼。

public String(char value[]) {
        this(value, 0, value.length, null);
    }
String(char[] value, int off, int len, Void sig) {
        if (len == 0) {
            this.value = "".value;
            this.coder = "".coder;
            return;
        }
        if (COMPACT_STRINGS) {
            byte[] val = StringUTF16.compress(value, off, len);
            if (val != null) {
                this.value = val;
                this.coder = LATIN1;
                return;
            }
        }
        this.coder = UTF16;
        this.value = StringUTF16.toBytes(value, off, len);
    }
複製程式碼

構造方法傳入 byte 陣列時,同時會傳入 charsetName,即編碼。核心操作為StringCoding.decode,它會先根據編碼對 byte 陣列進行解碼,解碼過程會判斷是否全部都在 LATIN1 編碼內,如果是則使用 LATIN1 編碼,否則使用 UTF16 編碼,並且將解碼後對應的 byte 陣列賦值給 String 物件的 value。

public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBoundsOffCount(offset, length, bytes.length);
        StringCoding.Result ret =
            StringCoding.decode(charsetName, bytes, offset, length);
        this.value = ret.value;
        this.coder = ret.coder;
    }
複製程式碼

主要方法

length方法

字串的長度應該是字元的長度,而不是位元組陣列的長度,所以這裡做了右移操作,LATIN1 編碼時coder()為0,字串長度等於位元組陣列長度。UTF16 編碼時coder()為1,字串等於位元組陣列長度一半。

public int length() {
        return value.length >> coder();
    }
複製程式碼

isEmpty方法

通過判斷 byte 陣列長度是否為0來判斷字串物件是否為空。

public boolean isEmpty() {
        return value.length == 0;
    }
複製程式碼

charAt方法

取字元需要根據編碼來操作,如果是 LATIN1 編碼,則直接取 byte 陣列中對應索引的元素,並轉成 char 型別即可。如果是 UTF16 編碼,因為它每個 UTF16 編碼佔用兩個位元組,所以需要將索引乘以2後作為最終索引取得兩個位元組並轉換成 char 型別,具體實現邏輯如getChar方法所示。

public char charAt(int index) {
        if (isLatin1()) {
            return StringLatin1.charAt(value, index);
        } else {
            return StringUTF16.charAt(value, index);
        }
    }
    
static char getChar(byte[] val, int index) {
        index <<= 1;
        return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
                      ((val[index]   & 0xff) << LO_BYTE_SHIFT));
    }
複製程式碼

codePointAt方法

獲取字串對應索引的 Unicode 程式碼點,根據編碼做不同處理。如果是 LATIN1 編碼,直接將 byte 陣列對應索引的元素與0xff做&操作並轉成 int 型別。相應的,UTF16 編碼也需要對應做轉換,它包含了兩個位元組。

public int codePointAt(int index) {
        if (isLatin1()) {
            checkIndex(index, value.length);
            return value[index] & 0xff;
        }
        int length = value.length >> 1;
        checkIndex(index, length);
        return StringUTF16.codePointAt(value, index, length);
    }
複製程式碼

codePointBefore方法

用於返回指定索引值前一個字元的程式碼點,實現與codePointAt方法類似,只是索引值要減1。

public int codePointBefore(int index) {
        int i = index - 1;
        if (i < 0 || i >= length()) {
            throw new StringIndexOutOfBoundsException(index);
        }
        if (isLatin1()) {
            return (value[i] & 0xff);
        }
        return StringUTF16.codePointBefore(value, index);
    }

複製程式碼

codePointCount方法

用於得到指定索引範圍內程式碼點的個數,如果是 Latin1 編碼則直接索引值相減,因為每個位元組肯定都屬於一個程式碼點。如果是 UTF16 編碼則要檢查是否存在 High-surrogate 程式碼和 Low-surrogate 程式碼,如果存在則說明需要4個位元組來表示一個字元,此時要把 count 減1。

public int codePointCount(int beginIndex, int endIndex) {
        if (beginIndex < 0 || beginIndex > endIndex ||
            endIndex > length()) {
            throw new IndexOutOfBoundsException();
        }
        if (isLatin1()) {
            return endIndex - beginIndex;
        }
        return StringUTF16.codePointCount(value, beginIndex, endIndex);
    }
    
private static int codePointCount(byte[] value, int beginIndex, int endIndex, boolean checked) {
        assert beginIndex <= endIndex;
        int count = endIndex - beginIndex;
        int i = beginIndex;
        if (checked && i < endIndex) {
            checkBoundsBeginEnd(i, endIndex, value);
        }
        for (; i < endIndex - 1; ) {
            if (Character.isHighSurrogate(getChar(value, i++)) &&
                Character.isLowSurrogate(getChar(value, i))) {
                count--;
                i++;
            }
        }
        return count;
    }

public static int codePointCount(byte[] value, int beginIndex, int endIndex) {
        return codePointCount(value, beginIndex, endIndex, false /* unchecked */);
    }
複製程式碼

offsetByCodePoints方法

該方法用於返回 String 中從給定的 index 處偏移 codePointOffset 個 Unicode 程式碼點的索引,要注意 Unicode 程式碼可能兩個位元組也可能四個位元組。邏輯為:

  • index 不能小於0且不能超過。
  • 當 codePointOffset 大於0時,通過 for 迴圈遞增索引值,判斷如果存在 High-surrogate 程式碼和 Low-surrogate 程式碼則索引值還需額外加1。
  • 當 codePointOffset 小於0時,通過 for 迴圈遞減索引值,判斷如果存在 High-surrogate 程式碼和 Low-surrogate 程式碼則索引值還需額外減1。
public int offsetByCodePoints(int index, int codePointOffset) {
        if (index < 0 || index > length()) {
            throw new IndexOutOfBoundsException();
        }
        return Character.offsetByCodePoints(this, index, codePointOffset);
    }

public static int offsetByCodePoints(CharSequence seq, int index,
                                         int codePointOffset) {
        int length = seq.length();
        if (index < 0 || index > length) {
            throw new IndexOutOfBoundsException();
        }

        int x = index;
        if (codePointOffset >= 0) {
            int i;
            for (i = 0; x < length && i < codePointOffset; i++) {
                if (isHighSurrogate(seq.charAt(x++)) && x < length &&
                    isLowSurrogate(seq.charAt(x))) {
                    x++;
                }
            }
            if (i < codePointOffset) {
                throw new IndexOutOfBoundsException();
            }
        } else {
            int i;
            for (i = codePointOffset; x > 0 && i < 0; i++) {
                if (isLowSurrogate(seq.charAt(--x)) && x > 0 &&
                    isHighSurrogate(seq.charAt(x-1))) {
                    x--;
                }
            }
            if (i < 0) {
                throw new IndexOutOfBoundsException();
            }
        }
        return x;
    }
複製程式碼

getChars方法

用於獲取字串物件指定範圍內的字元到目標 char 陣列中,主要是根據兩種編碼做不同處理,如果是 LATIN1 編碼則直接將 byte 陣列對應索引的元素與0xff做&操作並轉成 char 型別。而如果是 UTF16 編碼則需要兩個位元組一起轉為 char 型別。

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        checkBoundsBeginEnd(srcBegin, srcEnd, length());
        checkBoundsOffCount(dstBegin, srcEnd - srcBegin, dst.length);
        if (isLatin1()) {
            StringLatin1.getChars(value, srcBegin, srcEnd, dst, dstBegin);
        } else {
            StringUTF16.getChars(value, srcBegin, srcEnd, dst, dstBegin);
        }
    }
複製程式碼

getBytes方法

獲取字串指定編碼的位元組陣列,比如 charsetName 為 utf8,則將字串轉為 utf8 編碼後對應的位元組陣列。如果不傳引數則使用 JVM 預設編碼,即Charset.defaultCharset()

public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null) throw new NullPointerException();
        return StringCoding.encode(charsetName, coder(), value);
    }
public byte[] getBytes() {
        return StringCoding.encode(coder(), value);
    }
複製程式碼

equals方法

用於比較兩字串物件是否相等,如果引用相同則返回 true。否則判斷比較物件是否為 String 類的例項,是的話轉成 String 型別,接著比較編碼是否相同,分別以 LATIN1 編碼和 UTF16 編碼進行比較。

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (coder() == aString.coder()) {
                return isLatin1() ? StringLatin1.equals(value, aString.value)
                                  : StringUTF16.equals(value, aString.value);
            }
        }
        return false;
    }
複製程式碼

contentEquals方法

該方法用於比較字串之間內容是否相等,邏輯為:

  • 是否由 AbstractStringBuilder 例項化出來,是的話表示它為 StringBuilder 或 StringBuffer 物件。
  • 如果為 StringBuffer 物件,說明它是執行緒安全的,需要做同步處理,呼叫nonSyncContentEquals方法。
  • 如果為 StringBuilder 物件,它是非執行緒安全的,直接呼叫nonSyncContentEquals方法。
  • 如果為 String 物件,則呼叫equals方法比較。
  • 接下去的邏輯屬於 CharSequence 物件時的邏輯。如果長度不相等直接返回 false。
  • 如果為 Latin1 編碼,則只比較一個位元組。
  • 如果為 UTF16 編碼,則要兩個位元組地比較。
public boolean contentEquals(CharSequence cs) {
        if (cs instanceof AbstractStringBuilder) {
            if (cs instanceof StringBuffer) {
                synchronized(cs) {
                   return nonSyncContentEquals((AbstractStringBuilder)cs);
                }
            } else {
                return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        }
        if (cs instanceof String) {
            return equals(cs);
        }
        int n = cs.length();
        if (n != length()) {
            return false;
        }
        byte[] val = this.value;
        if (isLatin1()) {
            for (int i = 0; i < n; i++) {
                if ((val[i] & 0xff) != cs.charAt(i)) {
                    return false;
                }
            }
        } else {
            if (!StringUTF16.contentEquals(val, cs, n)) {
                return false;
            }
        }
        return true;
    }
複製程式碼

nonSyncContentEquals方法邏輯為:

  • 判斷兩個長度不相等則返回 false。
  • 如果兩者的編碼相同,則一個個位元組比較。
  • 兩者編碼不同時,如果本身編碼為 Latin1 編碼,那就直接返回 false,而如果本身為 UTF16 編碼,則兩個位元組地比較。
private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
        int len = length();
        if (len != sb.length()) {
            return false;
        }
        byte v1[] = value;
        byte v2[] = sb.getValue();
        if (coder() == sb.getCoder()) {
            int n = v1.length;
            for (int i = 0; i < n; i++) {
                if (v1[i] != v2[i]) {
                    return false;
                }
            }
        } else {
            if (!isLatin1()) { 
                return false;
            }
            return StringUTF16.contentEquals(v1, v2, len);
        }
        return true;
    }
複製程式碼

equalsIgnoreCase方法

該方法用於對比字串是否相等,而且是忽略大小寫。如果是自己與自己對比則不為空則為true,否則需要兩者長度相等且regionMatches方法返回true才為true。

public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.length() == length())
                && regionMatches(true, 0, anotherString, 0, length());
    }
複製程式碼

regionMatches方法邏輯為:

  • ignoreCase 如果為 false,即不忽略大小寫,則需要調另外一個regionMatches方法,這裡為 true,忽略此方法。
  • 校驗 offset 不能小於0且不能大於待比較長度。
  • coder() == other.coder()為 true,即兩者編碼一樣時,如果為 Latin1 編碼,則以 Latin1 方式比較,否則以 UTF16 方式比較。
  • 如果兩者編碼不同,則以 Latin1 編碼與 UTF16 編碼比較,主要就是將 Latin1 轉為 UTF16。而如果是 UTF16 編碼的話則將另外一個的 Latin1 轉為 UTF16 後比較。
public boolean regionMatches(boolean ignoreCase, int toffset,
            String other, int ooffset, int len) {
        if (!ignoreCase) {
            return regionMatches(toffset, other, ooffset, len);
        }
        if ((ooffset < 0) || (toffset < 0)
                || (toffset > (long)length() - len)
                || (ooffset > (long)other.length() - len)) {
            return false;
        }
        byte tv[] = value;
        byte ov[] = other.value;
        if (coder() == other.coder()) {
            return isLatin1()
              ? StringLatin1.regionMatchesCI(tv, toffset, ov, ooffset, len)
              : StringUTF16.regionMatchesCI(tv, toffset, ov, ooffset, len);
        }
        return isLatin1()
              ? StringLatin1.regionMatchesCI_UTF16(tv, toffset, ov, ooffset, len)
              : StringUTF16.regionMatchesCI_Latin1(tv, toffset, ov, ooffset, len);
    }
複製程式碼

compareTo方法

該方法用於比較兩個字串,主要的邏輯為:

  • coder() == anotherString.coder(),即兩者編碼相同時,如果為 Latin1 編碼則以 Latin1 的方式進行比較。否則以 UTF16 方式進行比較,具體如何比較下面以 Latin1 編碼為例子。
  • 兩者編碼不同時,則將 Latin1 編碼轉成 UTF16 後進行比較。
public int compareTo(String anotherString) {
        byte v1[] = value;
        byte v2[] = anotherString.value;
        if (coder() == anotherString.coder()) {
            return isLatin1() ? StringLatin1.compareTo(v1, v2)
                              : StringUTF16.compareTo(v1, v2);
        }
        return isLatin1() ? StringLatin1.compareToUTF16(v1, v2)
                          : StringUTF16.compareToLatin1(v1, v2);
     }
複製程式碼

Latin1 編碼的比較邏輯為:

  • 首先分別獲取兩者的長度。
  • 選取兩者最小的長度。
  • 開始遍歷尋找兩者不相同的字元,分別獲取對應的 char 值並相減,大於0則說明第一個字串大。
  • 如果遍歷完都相等,那麼就看誰的長度長,第一個字串長度較長就返回大於0。
public static int compareTo(byte[] value, byte[] other) {
        int len1 = value.length;
        int len2 = other.length;
        int lim = Math.min(len1, len2);
        for (int k = 0; k < lim; k++) {
            if (value[k] != other[k]) {
                return getChar(value, k) - getChar(other, k);
            }
        }
        return len1 - len2;
    }
複製程式碼

compareToIgnoreCase方法

該方法類似 compareTo 方法,只是忽略大小寫。實現通過CaseInsensitiveComparator內部類來實現。

public int compareToIgnoreCase(String str) {
        return CASE_INSENSITIVE_ORDER.compare(this, str);
    }
複製程式碼

regionMatches方法

該方法用於檢測指定區域字串是否相等,其邏輯為:

  • ignoreCase 如果為 false,即不忽略大小寫,則需要調另外一個regionMatches方法。
  • 校驗 offset 不能小於0且不能大於待比較長度。
  • coder() == other.coder()為 true,即兩者編碼一樣時,如果為 Latin1 編碼,則以 Latin1 方式比較,否則以 UTF16 方式比較。
  • 如果兩者編碼不同,則以 Latin1 編碼與 UTF16 編碼比較,主要就是將 Latin1 轉為 UTF16。而如果是 UTF16 編碼的話則將另外一個的 Latin1 轉為 UTF16 後比較。
public boolean regionMatches(boolean ignoreCase, int toffset,
            String other, int ooffset, int len) {
        if (!ignoreCase) {
            return regionMatches(toffset, other, ooffset, len);
        }
        if ((ooffset < 0) || (toffset < 0)
                || (toffset > (long)length() - len)
                || (ooffset > (long)other.length() - len)) {
            return false;
        }
        byte tv[] = value;
        byte ov[] = other.value;
        if (coder() == other.coder()) {
            return isLatin1()
              ? StringLatin1.regionMatchesCI(tv, toffset, ov, ooffset, len)
              : StringUTF16.regionMatchesCI(tv, toffset, ov, ooffset, len);
        }
        return isLatin1()
              ? StringLatin1.regionMatchesCI_UTF16(tv, toffset, ov, ooffset, len)
              : StringUTF16.regionMatchesCI_Latin1(tv, toffset, ov, ooffset, len);
    }
複製程式碼

大小寫敏感的比較邏輯:

  • 檢驗兩者的偏移的合法性,不能小於0,也不能超出特定長度。
  • 如果coder() == other.coder(),即兩者編碼相同時,如果為 Latin1 編碼則直接比較每個位元組,而如果為 UTF16 編碼則需要將位移和長度都擴大一倍,因為 UTF16 佔用的空間是 Latin1 的兩倍,然後再比較每個位元組是否相等。
  • 如果兩者編碼不相同時,不管兩者誰是 Latin1 還是 UTF16 編碼,都將它們轉換成 char 型別再比較。
public boolean regionMatches(int toffset, String other, int ooffset, int len) {
        byte tv[] = value;
        byte ov[] = other.value;
        if ((ooffset < 0) || (toffset < 0) ||
             (toffset > (long)length() - len) ||
             (ooffset > (long)other.length() - len)) {
            return false;
        }
        if (coder() == other.coder()) {
            if (!isLatin1() && (len > 0)) {
                toffset = toffset << 1;
                ooffset = ooffset << 1;
                len = len << 1;
            }
            while (len-- > 0) {
                if (tv[toffset++] != ov[ooffset++]) {
                    return false;
                }
            }
        } else {
            if (coder() == LATIN1) {
                while (len-- > 0) {
                    if (StringLatin1.getChar(tv, toffset++) !=
                        StringUTF16.getChar(ov, ooffset++)) {
                        return false;
                    }
                }
            } else {
                while (len-- > 0) {
                    if (StringUTF16.getChar(tv, toffset++) !=
                        StringLatin1.getChar(ov, ooffset++)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
複製程式碼

startsWith方法

該方法用於檢測字串是否以某個字首開始,並且可以指定偏移。邏輯為:

  • 檢查偏移和長度的合法性。
  • 如果coder() == prefix.coder(),即兩者編碼相同時,如果為 Latin1 編碼則直接比較每個位元組是否相等,如果為 UTF16 編碼則要將位移擴大一倍,再比較每個位元組。
  • 如果兩者編碼不相同,如果字串為 Latin1 編碼而字首字串為 UTF16 編碼則無法比較直接返回 false。如果字串為 UTF16 而字首字串為 Latin1,則轉為 UTF16 進行比較。
public boolean startsWith(String prefix) {
        return startsWith(prefix, 0);
    }
    
public boolean startsWith(String prefix, int toffset) {
        if (toffset < 0 || toffset > length() - prefix.length()) {
            return false;
        }
        byte ta[] = value;
        byte pa[] = prefix.value;
        int po = 0;
        int pc = pa.length;
        if (coder() == prefix.coder()) {
            int to = isLatin1() ? toffset : toffset << 1;
            while (po < pc) {
                if (ta[to++] != pa[po++]) {
                    return false;
                }
            }
        } else {
            if (isLatin1()) {  
                return false;
            }
            while (po < pc) {
                if (StringUTF16.getChar(ta, toffset++) != (pa[po++] & 0xff)) {
                    return false;
               }
            }
        }
        return true;
    }
複製程式碼

endsWith方法

該方法用於檢查是否以某個字串結尾,間接呼叫startsWith方法即可實現。

public boolean endsWith(String suffix) {
        return startsWith(suffix, length() - suffix.length());
    }
複製程式碼

hashCode方法

該方法返回字串物件的雜湊值,如果已經有快取了則直接返回,否則根據不同編碼分別計算雜湊值。

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            hash = h = isLatin1() ? StringLatin1.hashCode(value)
                                  : StringUTF16.hashCode(value);
        }
        return h;
    }
複製程式碼

下面分別是 Latin1 編碼和 UTF16 編碼的雜湊值計算邏輯,遍歷地執行h = 31 * h + (v & 0xff)h = 31 * h + getChar(value, i)運算。

public static int hashCode(byte[] value) {
        int h = 0;
        for (byte v : value) {
            h = 31 * h + (v & 0xff);
        }
        return h;
    }
    
public static int hashCode(byte[] value) {
        int h = 0;
        int length = value.length >> 1;
        for (int i = 0; i < length; i++) {
            h = 31 * h + getChar(value, i);
        }
        return h;
    }
複製程式碼

indexOf方法

該方法用於查詢字串中第一個出現某字元或字串的位置,有多種方法引數。可傳入 int 型別,也可傳入 String 型別,另外還能傳入開始位置。根據編碼的不同分別呼叫 StringLatin1 和 StringUTF16 的indexOf方法。

public int indexOf(int ch) {
        return indexOf(ch, 0);
    }
    
public int indexOf(int ch, int fromIndex) {
        return isLatin1() ? StringLatin1.indexOf(value, ch, fromIndex)
                          : StringUTF16.indexOf(value, ch, fromIndex);
    }
    
public int indexOf(String str) {
        if (coder() == str.coder()) {
            return isLatin1() ? StringLatin1.indexOf(value, str.value)
                              : StringUTF16.indexOf(value, str.value);
        }
        if (coder() == LATIN1) {  
            return -1;
        }
        return StringUTF16.indexOfLatin1(value, str.value);
    }
    
public int indexOf(String str, int fromIndex) {
        return indexOf(value, coder(), length(), str, fromIndex);
    }
複製程式碼

Latin1 編碼查詢邏輯,

  • 判斷 int 值是否能轉成 byte,方法是看右移8位是否為0,為0即說明除了低8位其他都為0。
  • 判斷索引值的合法性並修正。
  • int 值轉成 byte 型別。
  • 遍歷檢查陣列中哪個值相等並返回對應索引值。
  • 查詢不到就返回-1。
public static int indexOf(byte[] value, int ch, int fromIndex) {
        if (!canEncode(ch)) {
            return -1;
        }
        int max = value.length;
        if (fromIndex < 0) {
            fromIndex = 0;
        } else if (fromIndex >= max) {
            return -1;
        }
        byte c = (byte)ch;
        for (int i = fromIndex; i < max; i++) {
            if (value[i] == c) {
               return i;
            }
        }
        return -1;
    }

public static boolean canEncode(int cp) {
        return cp >>> 8 == 0;
    }
複製程式碼

類似地,對於 UTF16 編碼也做類似處理,但因為 unicode 包含了基本多語言平面(Basic Multilingual Plane,BMP)外,還存在補充平面。而傳入的值為 int 型別(4位元組),所以如果超出 BMP 平面,此時需要4個位元組,分別用來儲存 High-surrogate 和 Low-surrogate,此時就需要對比4個位元組。

另外,如果查詢子字串則是從子字串第一個字元開始匹配直到子字串完全被匹配成功。

-------------推薦閱讀------------

我的開源專案彙總(機器&深度學習、NLP、網路IO、AIML、mysql協議、chatbot)

為什麼寫《Tomcat核心設計剖析》

我的2017文章彙總——機器學習篇

我的2017文章彙總——Java及中介軟體

我的2017文章彙總——深度學習篇

我的2017文章彙總——JDK原始碼篇

我的2017文章彙總——自然語言處理篇

我的2017文章彙總——Java併發篇


跟我交流,向我提問:

從JDK原始碼看String(上)

歡迎關注:

從JDK原始碼看String(上)

相關文章