string的本質是char[ ]字符數組,String類只是封裝字符串的一些操做的,真是的字符串就是存在其下value這個字符數組中的。 java
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; //字符數組 //private final char[] value; //hash是String實例化的hashcode的一個緩存。字符串的不變性確保了hashcode的值一直是同樣的,在須要hashcode時,就不須要每次都計算,這樣會很高效。 private int hash; //Default to 0 private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; public String() { this.value = "".value; //或者能夠寫this.value = new char[0];構造裏只能初始化長度,不能賦值如: ={'A'}; } public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } //本質是持有一個靜態內部類,用於忽略大小寫得比較兩個字符串。 public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable {
public int compare(String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); //比較時忽略大小寫,同一索引的下標先都轉爲大寫比較,再轉爲小寫比較一次 c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } private Object readResolve() { return CASE_INSENSITIVE_ORDER; } } }
CASE_INSENSITIVE_ORDER 若是須要忽略大寫,比較時傳入此比較器便可,由於實現了Comparator比較器接口。【Comparator是比較器,實現Comparable的對象自身能夠直接使用比較】正則表達式
或者直接使用來持有這個內部類的公共的靜態變量 CASE_INSENSITIVE_ORDER,能夠簡單得用它來比較兩個String,這樣當要比較兩個String時能夠經過這個變量來調用。數據庫
而且String類中提供的compareToIgnoreCase方法其實就是調用這個內部類裏面的方法實現的。數組
public int compareToIgnoreCase(String str) { return CASE_INSENSITIVE_ORDER.compare(this, str); }
經過一個String 內部一個static的內部類實現的,那麼爲何還要特意寫一個內部類呢,這樣其實就是爲了代碼複用,這樣在其餘狀況下也可使用這個static內部類。緩存
由於String本質就是經過char[]實現的,能夠發現length(),isEmpty(),charAt()這些方法其實就是在內部調用數組的方法。安全
public int length() { return value.length; } public boolean isEmpty() { return value.length == 0; } public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; }
將String轉化成二進制:本質上是調用了StringCoding.encode()這個靜態方法。網絡
public byte[] getBytes() { return StringCoding.encode(value, 0, value.length); }
equals:app
首先進行當前對象和要判斷的對象引用的是否是同一個對象(==判斷引用地址),若是是則返回true。接着判斷要判斷的對象是否是String類的實例,若是是,接着轉化類型判斷兩個字符串的長度,若是同樣,進行char數組[]的逐一比較。
函數
public boolean equals(Object anObject) { if (this == anObject) { //首先進行當前對象和要判斷的對象引用的是否是同一個對象 return true; } if (anObject instanceof String) { String anotherString = (String)anObject; //判斷要判斷的對象是否是String類的實例 int n = value.length; if (n == anotherString.value.length) { //若是兩個字符串長度同樣,那麼一一比較char[] 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; }
hashCode: ui
hashCode方法能夠保證相同的字符串具備相同的hash值。可是hash值相同並不必定是字符串的value值相同。
public int hashCode() { int h = hash; //初始值爲0 if (h == 0 && value.length > 0) { char val[] = value; //字符數組 for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
其實,jdk1.7並無新的指令來處理switch string,而是經過調用switch中string.hashCode(),將string轉換爲int類型的hash值。而後用這個Hash值來惟一標識着這個case
。
當匹配的時候,首先調用這個字符串的hashCode()
方法,獲取一個Hash值(int類型),用這個Hash值來匹配全部的case
,若是沒有匹配成功,說明不存在;若是匹配成功了,接着會調用字符串的equals()
方法進行匹配。由於hashCode相同,字符串的value不必定相同
contentEqauls:
主要是用來比較String和StringBuffer或者StringBuild的內容是否同樣。能夠看到傳入參數是CharSequence ,這也說明了StringBuffer和StringBuild一樣是實現了CharSequence。源碼中先判斷參數是從哪個類實例化來的,再根據不一樣的狀況採用不一樣的方案,不過其實大致都是採用上面那個for循環的方式來進行判斷兩字符串是否內容相同。
public boolean contentEquals(CharSequence cs) { // Argument is a StringBuffer, StringBuilder if (cs instanceof AbstractStringBuilder) { if (cs instanceof StringBuffer) { synchronized(cs) { //若是是線程安全的StringBuffer,那麼會使用同步代碼鎖 return nonSyncContentEquals((AbstractStringBuilder)cs); } } else { //若是是StringBuilder return nonSyncContentEquals((AbstractStringBuilder)cs); } } // Argument is a String //若是被比較的只是Sting類型,那麼直接調用equals方法 if (cs instanceof String) { return equals(cs); } // Argument is a generic CharSequence //若是是一個字符序列CharSequence char v1[] = value; int n = v1.length; if (n != cs.length()) { //先比較長度 return false; } for (int i = 0; i < n; i++) { if (v1[i] != cs.charAt(i)) { //在比較每個字符 return false; } } return true; }
//字符串與字符串緩衝區比較 private boolean nonSyncContentEquals(AbstractStringBuilder sb) { char v1[] = value; char v2[] = sb.getValue(); int n = v1.length; if (n != sb.length()) { return false; } for (int i = 0; i < n; i++) { if (v1[i] != v2[i]) { return false; } } return true; }
compareTo:
這個就是String對Comparable接口中方法的實現了。
先經過比較兩個字符串的長度將最小的長度賦值給lim,接着將字符串賦值給兩個字符數組,在0~lim範圍內進行字符數組的逐一判斷,若是有一個不相等則返回兩個字符的ASCII碼的差值,若是循環結束都相等則返回兩個長度的差值。
其核心就是那個while循環,經過從第一個開始比較每個字符,當遇到第一個較小的字符時,斷定該字符串小。
注意:anotherString.value 不報錯是由於在本類中本類對象的引用可使用private變量
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; 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; }
startswith:判斷當前字符串是否以某一段其餘字符串開始的,和其餘字符串比較方法同樣,其實就是經過一個while來循環比較。
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. if ((toffset < 0) || (toffset > value.length - pc)) { return false; } while (--pc >= 0) { if (ta[to++] != pa[po++]) { return false; } } return true; }
contact:
concat的做用是將str拼接到當前字符串後面,經過代碼也能夠看出其實就是建一個新的字符串。
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
replace和replaceAll:
都是所有替換匹配的字符,而replaceAll是經過正則表達式的方式,替換全部匹配的字符
若是隻替換第一個匹配的字符,使用replaceFirst
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; } public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceAll(replacement); }
trim:
字符的比較實際上是比較ASCII碼值,而空字符對應的值是32,是最小的,比32小那麼就是空字符
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.valueOf(int i)與Integer.toString(int i)本質上沒什麼區別:
String: public static String valueOf(int i) { return Integer.toString(i); } Integer: public static String toString(int i) { if (i == Integer.MIN_VALUE) return "-2147483648"; int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); char[] buf = new char[size]; getChars(i, size, buf); return new String(buf, true); }
在 JDK 1.6 中,java.lang.String 主要由3 部分組成:表明字符數組的value、偏移量offset和長度count
char[] value offset 偏移 count 長度
字符串的實際內容由value、offset 和count 三者共同決定,而非value 一項。若是字符串value 數組包含100 個字符,而count 長度只有1 個字節,那麼這個String 實際上只有1 個字符,卻佔據了至少100 個字節,那剩餘的99 個就屬於泄漏的部分,它們不會被使用,不會被釋放,卻長期佔用內存,直到字符串自己被回收。能夠看到,str 的count 爲1,而它的實際取值爲字符串「0」,可是在value 的部分,卻包含了上萬個字節,在這個極端狀況中,本來只應該佔用1 個字節的String,卻佔用了上萬個字節,所以,能夠斷定爲內存泄漏。
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > count) { throw new StringIndexOutOfBoundsException(endIndex); } if (beginIndex > endIndex) { throw new StringIndexOutOfBoundsException(endIndex - beginIndex); } return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); } String(int offset, int count, char value[]) { //構造方法只是改變數組的偏移和長度,不是產生新的String對象,因此形成內存泄露 this.value = value; this.offset = offset; this.count = count; }
構造方法只是改變數組的偏移和長度,不是產生新的String對象,引用的仍是原字符串,原字符串永遠不會被回收,因此形成內存泄露
而在jdk1.7以後:
public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); } public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { if (count < 0) { throw new StringIndexOutOfBoundsException(count); } if (offset <= value.length) { this.value = "".value; return; } } // Note: offset or count might be near -1>>>1. if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } this.value = Arrays.copyOfRange(value, offset, offset+count); //複製建立了一個新的字符數組 }
能夠看到,在substring()的實現中,最終是使用了String 的構造函數,生成了一個新的String,不會形成內存泄露。
concat:鏈接string各個字符串,是建立新的String對象(字符數組),只能接受String
+:默認是java的String類的一種重載,將+後面的對象,轉換爲String類型,而後再進行字符串拼接,其實都是產生了一個新的對象,+能夠接其它類型
字符串拼接幾種方式的效率:+ < contact < StringBuffer < StringBuilder
String apple = "Apple,";
String fruit = apple + "Pear," + "Orange"
其實底層編譯器在執行上述代碼的時候會的自動引入 java.lang.StringBuilder 類,上面這個例子中,編譯器會建立一個 StringBuilder 對象,用來構造最終要生成的 String,併爲每個字符串調用一次 StringBuilder 中的 append() 方法,所以上述代碼一共執行了三次 append() 方法。最後調用 toString 生成最終的結果,並保存爲 fruit。
但能使用StringBuilder最好不要用 +,如再循環裏 += 鏈接會在每次循環自動建立一次StringBuilder對象,下降效率