String類被定義爲final類,意味着它不能被繼承,它是個不可變類,併發程序最喜歡不可變量了。java
String類實現了Serializable, Comparable<String>, CharSequence接口。面試
Comparable接口有compareTo(String s)方法,CharSequence接口有length(),charAt(int index),subSequence(int start,int end)方法。正則表達式
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
String類中包含一個不可變的char數組用來存放字符串,一個int型的變量hash用來存放計算後的哈希值。(String沒有length屬性,只有length方法,面試筆試常考)。 數組
String的字符串內容用的char數組來保存,而這個char數據變量也是final的,意味着字符串是不可變的,因此一個String一旦被聲明定義則是不可變的。更改String內容的本質就是:查看常量池中是否有對應的字符串,若是有則直接把變量引用到這個字符串上,若沒有,則新建一個字符串扔到常量池,而後對它進行變量引用。併發
/** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L;
//不含參數的構造函數,通常沒什麼用,由於value是不可變量 public String() { this.value = new char[0]; } //參數爲String類型 public String(String original) { this.value = original.value; this.hash = original.hash; } //參數爲char數組,使用java.utils包中的Arrays類複製 public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } //從bytes數組中的offset位置開始,將長度爲length的字節,以charsetName格式編碼,拷貝到value,在編碼轉換中比較經常使用 public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException { if (charsetName == null) throw new NullPointerException("charsetName"); checkBounds(bytes, offset, length); this.value = StringCoding.decode(charsetName, bytes, offset, length); } //調用public String(byte bytes[], int offset, int length, String charsetName)構造函數,在編碼轉換中比較經常使用 public String(byte bytes[], String charsetName) throws UnsupportedEncodingException { this(bytes, 0, bytes.length, charsetName); }
其他的構造函數不經常使用。函數
String類的方法不少,但不少都是互相調用的,這裏看幾個經常使用方法的源碼。this
String重寫了Object的equals方法,能夠發現比較的內容是字符串。先是看看是否同一個內存地址,而後再比較一下長度,最後再比較內容,很是嚴謹高效的邏輯。編碼
內存地址相同,則爲真。spa
若是對象類型不是String類型,則爲假。不然繼續判斷。code
若是對象長度不相等,則爲假。不然繼續判斷。
從後往前,判斷String類中char數組value的單個字符是否相等,有不相等則爲假。若是一直相等直到第一個數,則返回真。
public boolean equals(Object anObject) { //判斷是否同一個內存地址 if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; //長度是否相等 if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; //逐一比較內容 while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
String類中獲取字符串的長度經過length()方法,其實調用的是數組的length屬性,因此String沒有length屬性。
public int length() { return value.length; }
substring返回的也是一個新的String對象,由於從0開始計算,其中不包括endIndex位置的字符,幾乎全部String操做都會涉及new一個String對象,在有些場合記得從新引用,原來字符串的是不會改變的,因此能夠想象常量池裏面的內容是多麼的龐大,特別是大型的企業級項目,若是不注意合理使用String類的話,GC是很是頻繁的。
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }
String是如何去掉先後的空格的?就是截取先後不屬於空格內容的部分。
public String trim() { int len = value.length; int st = 0; char[] val = value; /* avoid getfield opcode */ while ((st < len) && (val[st] <= ' ')) { st++; } while ((st < len) && (val[len - 1] <= ' ')) { len--; } return ((st > 0) || (len < value.length)) ? substring(st, len) : this; }
String替換方法replace是在char數組內進行的,先定位到要替換字符的位置,而後把不須要替換的部分複製到一個新的char數組內,把要替換的部分替換成新的字符,而後利用新的char數組生成一個新的String對象返回。這個方法值替換第一個位置,所有替換有replaceAll()方法,而這是用正則表達式匹配替換的。
public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; }
這個方法寫的很巧妙,先從0開始判斷字符大小。若是兩個對象能比較字符的地方比較完了還相等,就直接返回自身長度減被比較對象長度,若是兩個字符串長度相等,則返回的是0,巧妙地判斷了三種狀況。因此比較字符跟字符的長度沒有什麼關係,並非字符串長度大的就會返回>0。
public int compareTo(String anotherString) { //自身對象字符串長度len1 int len1 = value.length; //被比較對象字符串長度len2 int len2 = anotherString.value.length; //取兩個字符串長度的最小值lim int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; //從value的第一個字符開始到最小長度lim處爲止,若是字符不相等,返回自身(對象不相等處字符-被比較對象不相等字符) while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } //若是前面都相等,則返回(自身長度-被比較對象長度) return len1 - len2; }
String類重寫了hashCode方法,Object中的hashCode方法是一個Native調用。String類的hash採用多項式計算得來,咱們徹底能夠經過不相同的字符串得出一樣的hash,因此兩個String對象的hashCode相同,並不表明兩個String是同樣的。
public int hashCode() { int h = hash; //若是hash沒有被計算過,而且字符串不爲空,則進行hashCode計算 if (h == 0 && value.length > 0) { char val[] = value; //計算過程 //s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } //hash賦值 hash = h; } return h; }
起始比較和末尾比較都是比較常常用獲得的方法,例如在判斷一個字符串是否是http協議的,或者初步判斷一個文件是否是mp3文件,均可以採用這個方法進行比較。
public boolean startsWith(String prefix, int toffset) { char ta[] = value; int to = toffset; char pa[] = prefix.value; int po = 0; int pc = prefix.value.length; // Note: toffset might be near -1>>>1. //若是起始地址小於0或者(起始地址+所比較對象長度)大於自身對象長度,返回假 if ((toffset < 0) || (toffset > value.length - pc)) { return false; } //從所比較對象的末尾開始比較 while (--pc >= 0) { if (ta[to++] != pa[po++]) { return false; } } return true; } public boolean startsWith(String prefix) { return startsWith(prefix, 0); } public boolean endsWith(String suffix) { return startsWith(suffix, value.length - suffix.value.length); }
String對象是不可變類型,返回類型爲String的String方法每次返回的都是新的String對象,除了某些方法的某些特定條件返回自身。
String對象的三種比較方式:
==內存比較:直接對比兩個引用所指向的內存值,精確簡潔直接明瞭。
equals字符串值比較:比較兩個引用所指對象字面值是否相等。
hashCode字符串數值化比較:將字符串數值化。兩個引用的hashCode相同,不保證內存必定相同,不保證字面值必定相同。
但願看一遍源碼,之後少在String中踩一下坑吧。