JDK10源碼閱讀——String

jdk源碼裡對String的介紹:

String 是不可變的,一旦被創建其值不能被改變. String buffers 支持可變String.

因為String是不可變的, 所以它們可以被共享.

例如:

String str = "abc";

等價於

char data[] = {'a', 'b', 'c'};
String str = new String(data);

源碼中提供的其他使用String的例子:

System.out.println("abc");
String cde = "cde";
System.out.println("abc" + cde);
String c = "abc".substring(2,3);
String d = cde.substring(1, 2);

String的方法包括檢查字符串裡的單個字符,比較字符串,搜索字符串,提取子字符串,創建字符串副本等.實例映射是基於Character中指定的Unicode標準.

Java語言為String的字符串連接符 + 提供特殊的支持, +也可以用於轉換其他類型的對象為String.

除非另有說明,否則將null作為參數傳遞給構造方法或者方法會拋出NullPointerException異常.

String表示一個字符串通過UTF-16(unicode)格式,補充字符通過代理對(參見Character類的 Unicode Character Representations 獲取更多的信息)表示。

索引值參考字符編碼單元,所以補充字符在String中佔兩個位置。

1. String的定義

String不能被繼承,因為String類有final修飾符.

同時String實現了3個接口:

  1. java.io.Serializable:String類可序列化(白話解釋: 把原本在內存中的對象狀態 變成可存儲或傳輸的過程稱之為序列化。序列化之後,就可以把序列化後的內容寫入磁盤,或者通過網絡傳輸到別的機器上. )
  2. Comparable:實現compareTo方法,String的對象列表(和數組)可以通過 Collections.sort(和 Arrays.sort )進行自動排序
  3. CharSequence:char值的一個可讀字符序列. java public final class String
  4. implements java.io.Serializable, Comparable, CharSequence

2. String的屬性

所有屬性如下圖:

JDK10源碼閱讀——String

value[]

@Stable
private final byte[] value;

value 用來存儲字符.(記得jdk8的這個value還是 char數組 不是byte數組. 特意下jdk9看了下,發現jdk9已經是byte數組了.)

從final關鍵字也可以看出,String一旦被初始化了就不能被更改.@Stable表示:表示此字段存儲在字段中的第一個非null(相應,非零)值永遠不會改變。( value不可能為null,從構造方法可以看出~ )

coder

private final byte coder;

code是value中字節的編碼標識符,現實中支持的是 LATIN1/UTF16

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

hash

private int hash;

緩存字符串的hashcode, 默認值為0

序列化serialVersionUID

private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

String實現了Serializable接口,所以支持序列化和反序列化支持。Java的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常(InvalidCastException)。

COMPACT_STRINGS

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

字符串壓縮,該字段值由JVM注入.

3. String的構造方法

String作為java中最長用到的類,有很多重載的構造方法,如下圖:

JDK10源碼閱讀——String

無參構造方法

構造一個空字符串,值得注意的是:因為String是不可變的,所以不必要使用這個構造方法

public String() {
this.value = "".value;
this.coder = "".coder;
}

以String為參數

初始化新創建的String對象,與其參數相同.(其實就是參數字符串的副本)

public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}

使用字符數組組構造String

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) {
// compressedCopy char[] -> byte[]
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);
}

String(char[] value, int off, int len, Void sig) 是私有構造方法 Void sig為了消除其他共有構造方法的歧義.

將char[]值存儲到byte[]中.如果char[] 中僅包含latin1字符,則每個字節代表相應字符的8個低位. 或者是由一個byte[]存儲StringUTF16中定義的字節序列中的所有字符.

在使用字符數組來創建一個新的String對象的時候,不僅可以使用整個字符數組,也可以使用字符數組的一部分,只要多傳入兩個參數int offset和int count就可以了,如下:

public String(char value[], int offset, int count) {
this(value, offset, count, rangeCheck(value, offset, count));
}

offset是第一個字符的索引

count是子數組的長度.

通過字節數組構造String

JDK10源碼閱讀——String

jdk10中,String內部就是用byte[]存儲,上圖紅框中的構造方法中大同小異,

都是以byte[]為參數,兩個int分別是:其實位置和子byte[]長度.

參數byte[]保存到String的byte[]中需要再次進行解碼.String參數或者Charset參數是指定的字符集.

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

如果沒有指定字符集,默認使用ISO-8859-1進行解碼.

 static Result decode(byte[] ba, int off, int len) {
String csn = Charset.defaultCharset().name();
try {
// use charset name decode() variant which provides caching.
return decode(csn, ba, off, len);
} catch (UnsupportedEncodingException x) {
warnUnsupportedCharset(csn);
}
try {
return decode("ISO-8859-1", ba, off, len);
} catch (UnsupportedEncodingException x) {
// If this code is hit during VM initialization, err(String) is
// the only way we will be able to get any kind of error message.
err("ISO-8859-1 charset not available: " + x.toString() + "\n");
// If we can not find ISO-8859-1 (a required encoding) then things
// are seriously wrong with the installation.
System.exit(1);
return null;
}
}

還有用StringBuilder和StringBuffer作為參數構造String, 但是基本不這樣用,因為StringBuilder和StringBuffer都有.toString()方法.

4. String的一些方法

length

返回字符串的長度

public int length() { 

return value.length >> coder();
}

isEmpty

判斷字符串是否為空

public boolean isEmpty() {
return value.length == 0;
}

charAt

返回索引處的char值,範圍為 0到length()-1,

序列第一位為0,下一位為1,依此類推

public char charAt(int index) {
if (isLatin1()) {
return StringLatin1.charAt(value, index);
} else {
return StringUTF16.charAt(value, index);
}
}

equals

字符串與指定對象比較,結果為true表示參數是String對象並且與此對象有相同的字符序列.

 public boolean equals(Object anObject) {
//同一個對象,為真
if (this == anObject) {
return true;
}

if (anObject instanceof String) {//String類型
String aString = (String)anObject;
if (coder() == aString.coder()) {//並且編碼相同
//不同的編碼,不同的比較方式
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}

codePointAt

返回指定索引處的字符(Unicode code值.範圍為 0到length()-1,

序列第一位為0,下一位為1,依此類推

public int codePointAt(int index) {
//latin1字符集
if (isLatin1()) {
checkIndex(index, value.length);
return value[index] & 0xff;
}
int length = value.length >> 1;
checkIndex(index, length);
// utf16字符集
return StringUTF16.codePointAt(value, index, length);
}
//例如:
System.out.println("A".codePointAt(0)); //結果為 65

offsetByCodePoints

通過Unicode code值返回索引偏移位置

public int offsetByCodePoints(int index, int codePointOffset) {
if (index < 0 || index > length()) {

throw new IndexOutOfBoundsException();
}
return Character.offsetByCodePoints(this, index, codePointOffset);
}

getChars

將此字符串中的字符複製到目標字符數組中.

/**
* @param srcBegin要複製的字符串中第一個字符的索引。
* @param srcEnd索引要複製的字符串中的最後一個字符。
* @param dst目標數組。
* @param dstBegin目標數組中的起始偏移量。
* */
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

按照指定的字符集把String轉成字節數組返回.

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

參數也可以是java.nio.charset.Charset 或者無參使用默認字符集

JDK10源碼閱讀——String

contentEquals

字符串與指定的CharSequence進行比較,只有當此字符串與參數有相同的char值序列是返回true.

可以看到方法中根據參數的類型不同而採用不同的比較方法,AbstractStringBuilder,String 都實現了CharSequence接口.

這個方法包含了equals(String)方法,當參數類型為String時,複用equals方法.

 public boolean contentEquals(StringBuffer sb) {
return contentEquals((CharSequence)sb);
}
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);
}
// Argument is a generic CharSequence
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;
}

comepareTo

按字典順序比較兩個字符串。比較基於字符串中每個字符的Unicode值.

如果當前String對象按照字典順序在參數字符串之前,則結果為負整數.

反之為負整數.

p

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

@

param toffset此字符串中子區域的起始偏移量。
@param其他字符串參數。
@param ooffset字符串中子區域的起始偏移量
論據。
@param len要比較的字符數。
@return 如果指定該字符串的子區域完全匹配字符串參數的指定子區域返回true,否則返回false;
public boolean regionMatches(int toffset, String other, int ooffset, int len)

從索引toffset開始的當前字符串的子串 是否以制定的prefix前綴開頭.

public boolean startsWith(String prefix, int toffset)

返回指定子字符串第一次出現的字符串中的索引。

public int indexOf(String str)

返回當前字符串的子串,從索引處開始直到字符串結尾.

public String substring(int beginIndex)

例如:

"unhappy".substring(2) 返回 "happy"

"Harbison".substring(3) 返回 "bison"

"emptiness".substring(9) 返回 "" (an empty string)

返回一個子字符串,子字符串從指定的beginIndex開始,並擴展到索引endIndex-1處的字符。 因此子串的長度是endIndex-beginIndex

 public String substring(int beginIndex, int endIndex) {
int length = length();
checkBoundsBeginEnd(beginIndex, endIndex, length);
int subLen = endIndex - beginIndex;
if (beginIndex == 0 && endIndex == length) {
return this;
}
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
: StringUTF16.newString(value, beginIndex, subLen);
}


例如:
"hamburger".substring(4, 8) 返回 "urge"
"smiles".substring(1, 5) 返回 "mile"



將參數字符串連接到此字符串的末尾

public String concat(String str)

例如:
"cares".concat("s") 返回 "caress"
"to".concat("get").concat("her") 返回 "together"


返回替換所有出現的字符串.
如果當前字符串中未出現oldChar,則返回當前字符串.
`每次`出現的`oldChar` 都會被`newChar`替換.
public String replace(char oldChar, char newChar)
例如:
"the war of baronets".replace('r', 'y')
返回 "the way of bayonets"


判斷此字符串是否與指定字符串匹配
public boolean matches(String regex)



把當前字符串按照`regex`參數拆分,返回一個`String`數組.
數組中的子串按它們在此字符串中出現的順序排列.
如果匹配不到`regex`則數組中只有一個元素,就是該字符串.
limit 參數控制模式應用的次數,因此影響所得數組的長度。如果該限制 n 大於 0,則模式將被最多應用 n - 1 次,數組的長度將不會大於 n,而且數組的最後一項將包含所有超出最後匹配的定界符的輸入。如果 n 為非正,那麼模式將被應用盡可能多的次數,而且數組可以是任何長度。如果 n 為 0,那麼模式將被應用盡可能多的次數,數組可以是任何長度,並且結尾空字符串將被丟棄。

public String[] split(String regex, int limit)
例如:
String ss = "1,,5,6";
String sss[] = ss.split(",",3);
System.out.println(sss.length);//返回數組長度3,後面的5,6會被合併為一項

把字符串所有字符轉成(大寫/小寫) 返回新的字符串.

public String toLowerCase()
public String toUpperCase()

其他對象轉成String

p

ublic static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(char data[]) {
return new String(data);
}
public static String valueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
public static String valueOf(char c) {
if (COMPACT_STRINGS && StringLatin1.canEncode(c)) {
return new String(StringLatin1.toBytes(c), LATIN1);
}
return new String(StringUTF16.toBytes(c), UTF16);
}
public static String valueOf(int i) {
return Integer.toString(i);
}
public static String valueOf(long l) {
return Long.toString(l);
}
public static String valueOf(float f) {
return Float.toString(f);
}

public static String valueOf(double d) {
return Double.toString(d);
}

返回此字符串的hashCode.

public int hashCode()

hashcode計算公式:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

s[i]是string的第i個字符,n是String的長度。計算機的乘法涉及到移位計算,因為是二進制所以,當一個數乘以2時,就直接拿該數左移一位即可!選擇31原因是因為31是一個素數!

在存儲數據計算hash地址的時候,我們希望儘量減少有同樣的hash地址,所謂“衝突”。如果使用相同hash地址的數據過多,那麼這些數據所組成的hash鏈就更長,從而降低了查詢效率!所以在選擇係數的時候要選擇儘量長的係數並且讓乘法儘量不要溢出的係數,因為如果計算出來的hash地址越大,所謂的“衝突”就越少,查找起來效率也會提高。

31可以 由i*31== (i<<5)-1來表示,現在很多虛擬機裡面都有做相關優化,使用31的原因可能是為了更好的分配hash地址,並且31只佔用5bits!

在java乘法中如果數字相乘過大會導致溢出的問題,從而導致數據的丟失.

最近公司和家裡的事情都比較多,抽時間看了下String的源碼,並不是非常的細緻. 其實可以發現,因為String內部兩種不同的編碼集, 好多主要的代碼都在StringLatin1中和StringUTF16中.


分享到:


相關文章: