平常工作中String字元串對象用的那麼多,但是真的瞭解String嗎? 源碼淺析先從String開刀。 想要真正瞭解一個類,首先得從源碼入手。(本文JDK源碼版本1.7.0_75) 先看類信息: 由源碼可以看出: final修飾了String類,表明該類不能被其他類繼承。 String 實現了Se ...
平常工作中String字元串對象用的那麼多,但是真的瞭解String嗎?
源碼淺析先從String開刀。
想要真正瞭解一個類,首先得從源碼入手。(本文JDK源碼版本1.7.0_75)
先看類信息:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
由源碼可以看出:
final修飾了String類,表明該類不能被其他類繼承。
String 實現了Serializable介面,支持java序列化操作。
實現了Comparable介面,就必須實現介面下的compareTo方法,說明String對象支持比較,可對其排序。
再一個CharSequence,是字元序列,包括length(), charAt(int index), subSequence(int start, int end)幾個介面。通過下麵對String類中屬性也可以知道,其實String的本質就是字元數組,所以實現了CharSequence介面。
繼續看類中的屬性:
private final char value[]; //存放字元串的字元數組 private final int offset; //字元數組第一個下標的偏移量 private final int count;//String中的字元數量 private int hash; // string的hashcode值
value、offset和count屬性都是final修飾的,說明瞭這幾個屬性都是不可變的,而value數組存儲的字元串對象的實際內容,由此說明String對象是不可變的。不可變的對象怎麼做修改操作呢?下麵會解答。
從value[]就能看出String的實質了,就是字元數組,數組的大小就是字元的數量。那int類型的hash值是幹嘛的?存放hashcode,而hashcode的存在是便於快捷查找。比如在HashMap、HashTable等集合容器中,字元串作為key時,就是通過字元串的hashcode在散列結構中定位存儲位置的。
String中方法眾多,不一一分析了,著重看看幾個重要的方法。
字元串比較方法(用的很頻繁,如果連源碼都沒看過,豈不是很low?)
1 public boolean equals(Object anObject) { 2 if (this == anObject) { 3 return true; 4 } 5 if (anObject instanceof String) { 6 String anotherString = (String) anObject; 7 int n = count; 8 if (n == anotherString.count) { 9 char v1[] = value; 10 char v2[] = anotherString.value; 11 int i = offset; 12 int j = anotherString.offset; 13 while (n-- != 0) { 14 if (v1[i++] != v2[j++]) 15 return false; 16 } 17 return true; 18 } 19 } 20 return false; 21 }
傳入Object對象(Object是所有對象的父類),比較本類的引用地址和傳入的引用地址是否相等,引用地址相等說明就是一個對象,返回true。
如果不等,判斷傳入的對象是不是String類型(不是一類的就沒什麼可比性了),接著再比較字元串的長度,長度一樣則一一比較字元數組中的內容,數組中的內容全部一致則返回true。
比較順序是引用地址->類類型->字元長度->字元數組,而不是直接比較字元數組中的內容,最大化的提高比較效率。
下麵看計算hash值的方法
1 public int hashCode() { 2 int h = hash; 3 if (h == 0 && count > 0) { 4 int off = offset; 5 char val[] = value; 6 int len = count; 7 8 for (int i = 0; i < len; i++) { 9 h = 31 * h + val[off++]; 10 } 11 hash = h; 12 } 13 return h; 14 }
這個方法是計算hash值並傳給hash屬性。第一次調hashcode方法時,hash值是0,需要計算hash。根據源碼也看的很清楚,得出hash值的計算公式:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]。問題來了,為什麼取31為權重呢?主要是因為31是一個奇質數,並且31*i=32*i-i=(i<<5)-i,這種位移與減法結合的計算在電腦中相比一般的運算要快很多。
hash值的作用主要是定位查找對象,判斷迴文數也可以使用String類的hashcode是否相等進行判斷。
字元串操作方法
截取字元串
1 public String substring(int beginIndex, int endIndex) { 2 if (beginIndex < 0) { 3 throw new StringIndexOutOfBoundsException(beginIndex); 4 } 5 if (endIndex > count) { 6 throw new StringIndexOutOfBoundsException(endIndex); 7 } 8 if (beginIndex > endIndex) { 9 throw new StringIndexOutOfBoundsException(endIndex - beginIndex); 10 } 11 return ((beginIndex == 0) && (endIndex == count)) ? this 12 : new String(offset + beginIndex, endIndex - beginIndex, value); 13 }
字元串拼接
1 public String concat(String str) { 2 int otherLen = str.length(); 3 if (otherLen == 0) { 4 return this; 5 } 6 int len = value.length; 7 char buf[] = Arrays.copyOf(value, len + otherLen); 8 str.getChars(buf, len); 9 return new String(buf, true); 10 }
替換字元串中的內容
1 public String replace(char oldChar, char newChar) { 2 if (oldChar != newChar) { 3 int len = value.length; 4 int i = -1; 5 char[] val = value; /* avoid getfield opcode */ 6 7 while (++i < len) { 8 if (val[i] == oldChar) { 9 break; 10 } 11 } 12 if (i < len) { 13 char buf[] = new char[len]; 14 for (int j = 0; j < i; j++) { 15 buf[j] = val[j]; 16 } 17 while (i < len) { 18 char c = val[i]; 19 buf[i] = (c == oldChar) ? newChar : c; 20 i++; 21 } 22 return new String(buf, true); 23 } 24 } 25 return this; 26 }
字元串截取方法,先判斷截取位置是否越界,再返回新的字元串對象。再看其他對字元串操作的方法,replace、concat等其他方法,都有個共同點,就是對字元串的操作時原字元串並不會改變,都是在新生成的字元串對象上操作的。
String對象一旦被創建就是固定不可改變的,對String對象的任何操作都不影響到原對象,相關的更改操作都會生成新的字元串對象。
總結
1、String類初始化的對象是不可變的
因為String類是final修飾的,字元串一旦創建就不可改變。對字元串對象的修改操作都是新建字元串對象,不會對原對象更改。
2、String的hashcode並不能直接用於判斷字元串是否相等,但能用於字元串在容器存儲定位查找。
3、本篇涉及的內容有限,String創建對象在java記憶體中的引用並未涉及,以後再補充。