Java 源碼刨析 - String
【String 是如何實現的?它有哪些重要的方法?】
String 內部實際存儲結構為 char 數組,源碼如下:
|
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { // 用於存儲字元串的值 private final char value[]; // 快取字元串的 hash code private int hash; // Default to 0 // ......其他內容 } |
String 源碼中包含下面幾個重要的方法。
1. 多構造方法
|
// String 為參數的構造方法 public String(String original) { this.value = original.value; this.hash = original.hash; } // char[] 為參數構造方法 public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } // StringBuffer 為參數的構造方法 public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } } // StringBuilder 為參數的構造方法 public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); } |
2. equals() 比較兩個字元串是否相等
|
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; if (n == anotherString.value.length) { // 把兩個字元串都轉換為 char 數組對比 char v1[] = value; char v2[] = anotherString.value; int i = 0; // 循環比對兩個字元串的每一個字元 while (n-- != 0) { // 如果其中有一個字元不相等就 true false,否則繼續對比 if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } |
String 類型重寫了 Object 中的 equals() 方法,equals() 方法需要傳遞一個 Object 類型的參數值,在比較時會先通過 instanceof 判斷是否為 String 類型,如果不是則會直接返回 false,instanceof 的使用如下:
|
Object oString = "123"; Object oInt = 123; System.out.println(oString instanceof String); // 返回 true System.out.println(oInt instanceof String); // 返回 false |
還有一個和 equals() 比較類似的方法 equalsIgnoreCase(),它是用於忽略字元串的大小寫之後進行字元串對比。
3. compareTo() 比較兩個字元串
compareTo() 方法用於比較兩個字元串,返回的結果為 int 類型的值,源碼如下:
|
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; // 獲取到兩個字元串長度最短的那個 int 值 int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; // 對比每一個字元 while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { // 有字元不相等就返回差值 return c1 - c2; } k++; } return len1 - len2; } |
從源碼中可以看出,compareTo() 方法會循環對比所有的字元,當兩個字元串中有任意一個字元不相同時,則 return char1-char2。
還有一個和 compareTo() 比較類似的方法 compareToIgnoreCase(),用於忽略大小寫後比較兩個字元串。
可以看出 compareTo() 方法和 equals() 方法都是用於比較兩個字元串的,但它們有兩點不同:
equals() 可以接收一個 Object 類型的參數,而 compareTo() 只能接收一個 String 類型的參數;
equals() 返回值為 Boolean,而 compareTo() 的返回值則為 int。
它們都可以用於兩個字元串的比較,當 equals() 方法返回 true 時,或者是 compareTo() 方法返回 0 時,則表示兩個字元串完全相同。
4. 其他重要方法
|
indexOf |
查詢字元串首次出現的下標位置 |
|
lastIndexOf |
查詢字元串最後出現的下標位置 |
|
contains |
查詢字元串中是否包含另一個字元串 |
|
toLowerCase |
把字元串全部轉換成小寫 |
|
toUpperCase |
把字元串全部轉換成大寫 |
|
length |
查詢字元串的長度 |
|
trim |
去掉字元串首尾空格 |
|
replace |
替換字元串中的某些字元 |
|
split |
把字元串分割並返回字元串數組 |
|
join |
把字元串數組轉為字元串 |
【== 和 equals 的區別】
== 對於基本數據類型來說,是用於比較 "值"是否相等的;而對於引用類型來說,是用於比較引用地址是否相同的。
查看源碼我們可以知道 Object 中也有 equals() 方法,源碼如下:
|
public boolean equals(Object obj) { return (this == obj); } |
可以看出,Object 中的 equals() 方法其實就是 ==,而 String 重寫了 equals() 方法把它修改成比較兩個字元串的值是否相等。
【final 修飾的好處】
從 String 類的源碼我們可以看出 String 是被 final 修飾的不可繼承類,源碼如下:
|
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { //...... } |
那這樣設計有什麼好處呢?
Java 語言之父 James Gosling 的回答是,他會更傾向於使用 final,因為它能夠快取結果,當你在傳參時不需要考慮誰會修改它的值;如果是可變類的話,則有可能需要重新拷貝出來一個新值進行傳參,這樣在性能上就會有一定的損失。
James Gosling 還說迫使 String 類設計成不可變的另一個原因是安全,當你在調用其他方法時,比如調用一些系統級操作指令之前,可能會有一系列校驗,如果是可變類的話,可能在你校驗過後,它的內部的值又被改變了,這樣有可能會引起嚴重的系統崩潰問題,這是迫使 String 類設計成不可變類的一個重要原因。
總結來說,使用 final 修飾的第一個好處是安全;第二個好處是高效,以 JVM 中的字元串常量池來舉例,如下兩個變數:
|
String s1 = "java"; String s2 = "java"; |
只有字元串是不可變時,我們才能實現字元串常量池,字元串常量池可以為我們快取字元串,提高程式的運行效率,如下圖所示:

試想一下如果 String 是可變的,那當 s1 的值修改之後,s2 的值也跟著改變了,這樣就和我們預期的結果不相符了,因此也就沒有辦法實現字元串常量池的功能了。
【String 和 StringBuilder、StringBuffer 的區別】
因為 String 類型是不可變的,所以在字元串拼接的時候如果使用 String 的話性能會很低,
因此我們就需要使用另一個數據類型 StringBuffer,它提供了 append 和 insert 方法可用於字元串的拼接,它使用 synchronized 來保證執行緒安全,如下源碼所示:
|
@Override public synchronized StringBuffer append(Object obj) { toStringCache = null; super.append(String.valueOf(obj)); return this; }
@Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } |
因為它使用了 synchronized 來保證執行緒安全,所以性能不是很高;
於是在 JDK 1.5 就有了 StringBuilder,它同樣提供了 append 和 insert 的拼接方法,但它沒有使用 synchronized 來修飾,因此在性能上要優於 StringBuffer,所以在非並發操作的環境下可使用 StringBuilder 來進行字元串拼接。
【String 和 JVM】
String 常見的創建方式有兩種,new String() 的方式和直接賦值的方式;
直接賦值的方式會先去字元串常量池中查找是否已經有此值,如果有則把引用地址直接指向此值,否則會先在常量池中創建,然後再把引用指向此值;
而 new String() 的方式一定會先在堆上創建一個字元串對象,然後再去常量池中查詢此字元串的值是否已經存在,如果不存在會先在常量池中創建此字元串,然後把引用的值指向此字元串,如下程式碼所示:
|
String s1 = new String("Java"); String s2 = s1.intern(); String s3 = "Java"; System.out.println(s1 == s2); // false System.out.println(s2 == s3); // true |
它們在 JVM 存儲的位置,如下圖所示:

JDK 1.7 之後把永生代換成了元空間,把字元串常量池從方法區移到了 Java 堆上。
除此之外編譯器還會對 String 字元串做一些優化,例如以下程式碼:
|
String s1 = "Ja" + "va"; String s2 = "Java"; System.out.println(s1 == s2); |
雖然 s1 拼接了多個字元串,但對比的結果卻是 true。
程式碼 "Ja"+"va" 被直接編譯成了 "Java" ,因此 s1==s2 的結果才是 true,這就是編譯器對字元串優化的結果。
from:Java 源碼剖析(//kaiwu.lagou.com/course/courseInfo.htm?courseId=59#/detail/pc?id=1761)

