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();
複製代碼
LATIN1
或UTF16
。該內部類主要是提供排序的比較器,實現了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;
}
複製代碼
字符串的長度應該是字符的長度,而不是字節數組的長度,因此這裏作了右移操做,LATIN1 編碼時coder()
爲0,字符串長度等於字節數組長度。UTF16 編碼時coder()
爲1,字符串等於字節數組長度一半。
public int length() {
return value.length >> coder();
}
複製代碼
經過判斷 byte 數組長度是否爲0來判斷字符串對象是否爲空。
public boolean isEmpty() {
return value.length == 0;
}
複製代碼
取字符須要根據編碼來操做,若是是 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));
}
複製代碼
獲取字符串對應索引的 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);
}
複製代碼
用於返回指定索引值前一個字符的代碼點,實現與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);
}
複製代碼
用於獲得指定索引範圍內代碼點的個數,若是是 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 */);
}
複製代碼
該方法用於返回 String 中從給定的 index 處偏移 codePointOffset 個 Unicode 代碼點的索引,要注意 Unicode 代碼可能兩個字節也可能四個字節。邏輯爲:
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;
}
複製代碼
用於獲取字符串對象指定範圍內的字符到目標 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);
}
}
複製代碼
獲取字符串指定編碼的字節數組,好比 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);
}
複製代碼
用於比較兩字符串對象是否相等,若是引用相同則返回 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;
}
複製代碼
該方法用於比較字符串之間內容是否相等,邏輯爲:
nonSyncContentEquals
方法。nonSyncContentEquals
方法。equals
方法比較。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
方法邏輯爲:
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;
}
複製代碼
該方法用於對比字符串是否相等,並且是忽略大小寫。若是是本身與本身對比則不爲空則爲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
方法邏輯爲:
regionMatches
方法,這裏爲 true,忽略此方法。coder() == other.coder()
爲 true,即二者編碼同樣時,若是爲 Latin1 編碼,則以 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);
}
複製代碼
該方法用於比較兩個字符串,主要的邏輯爲:
coder() == anotherString.coder()
,即二者編碼相同時,若是爲 Latin1 編碼則以 Latin1 的方式進行比較。不然以 UTF16 方式進行比較,具體如何比較下面以 Latin1 編碼爲例子。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 編碼的比較邏輯爲:
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;
}
複製代碼
該方法相似 compareTo 方法,只是忽略大小寫。實現經過CaseInsensitiveComparator
內部類來實現。
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
複製代碼
該方法用於檢測指定區域字符串是否相等,其邏輯爲:
regionMatches
方法。coder() == other.coder()
爲 true,即二者編碼同樣時,若是爲 Latin1 編碼,則以 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);
}
複製代碼
大小寫敏感的比較邏輯:
coder() == other.coder()
,即二者編碼相同時,若是爲 Latin1 編碼則直接比較每一個字節,而若是爲 UTF16 編碼則須要將位移和長度都擴大一倍,由於 UTF16 佔用的空間是 Latin1 的兩倍,而後再比較每一個字節是否相等。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;
}
複製代碼
該方法用於檢測字符串是否以某個前綴開始,而且能夠指定偏移。邏輯爲:
coder() == prefix.coder()
,即二者編碼相同時,若是爲 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;
}
複製代碼
該方法用於檢查是否以某個字符串結尾,間接調用startsWith
方法便可實現。
public boolean endsWith(String suffix) {
return startsWith(suffix, length() - suffix.length());
}
複製代碼
該方法返回字符串對象的哈希值,若是已經有緩存了則直接返回,不然根據不一樣編碼分別計算哈希值。
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;
}
複製代碼
該方法用於查找字符串中第一個出現某字符或字符串的位置,有多種方法參數。可傳入 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 編碼查找邏輯,
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)
跟我交流,向我提問:
歡迎關注: