從JDK源碼看String(上)

概況

Java 語言使用 String 類用來表明字符串,實際上 String 對象的值是一個常量,一旦建立後不能被改變。正式由於其不可變,因此它是線程安全地,能夠多個線程共享。java

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

繼承結構

--java.lang.Object
  --java.lang.String
複製代碼

類定義

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

String 類被聲明爲 final,說明它不能再被繼承。同時它實現了三個接口,分別爲 Serializable、Comparable 和 CharSequence。其中 Serializable 接口代表其能夠序列化;sql

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 進行賦值。bash

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併發篇


跟我交流,向我提問:

歡迎關注:

相關文章
相關標籤/搜索