前言:String類在日常開發過程中使用頻率非常高,平時大家可能看過String的源碼,但是真的認真了解過它么,筆者在一次筆試過程中要求寫出String的equals方法,瞬間有點懵逼,憑着大致的理解,算是寫出來了,可是下來一翻String的源碼頓悟,原來自己寫得是多么的low,所以有必要把這些基礎知識點記錄下來,加深印象。
注:本文jdk源碼版本為jdk1.8.0_172
1.String類的基本概念
首先String是不可變對象,其體現主要在String類是被final關鍵字修飾,因此該對象不能被繼承,不能被修改。那你可能要問了為什么我們在日常操作過程中不是可以很方便的修改String的內容嗎?其實我們修改其內容是new了一個對象,原來的內容並沒有改變。
String內部是通過char數組來存儲的內容,從以下源碼中可以發現:
注:這里的char數組也是被final修飾。
2.String的構造函數
不知道你注意沒,String的構造函數非常的多:
這里挑選幾個筆者認為有特點的構造函數進行分析,其它構造函數請查看相應源碼。
#1.默認構造函數:
1 public String() { 2 this.value = "".value; 3 }
注:默認構造函數的實現非常簡單,就是返回空字符串的數組形式。
#2.入參為char數組:
1 public String(char value[]) { 2 this.value = Arrays.copyOf(value, value.length); 3 }
分析:如果傳入char數組,是通過Arrays#copyOf方法進行拷貝的。
#3.入參為String對象:
1 public String(String original) { 2 this.value = original.value; 3 this.hash = original.hash; 4 }
分析:如果入參為String對象,則直接進行相應的賦值即可(value和hash值)。
#4.入參為StringBuffer:
1 public String(StringBuffer buffer) { 2 synchronized(buffer) { 3 this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); 4 } 5 }
分析:這里對StringBuffer進行了加鎖,然后再進行拷貝操作。為什么要對StringBuffer加鎖呢?StringBuffer不是線程安全的嗎?其實這里對其進行加鎖正是為了保證在多線程環境下只能有一個線程去操作StringBuffer對象,這里需要注意一下。
#5.入參為StringBuilder:
1 public String(StringBuilder builder) { 2 this.value = Arrays.copyOf(builder.getValue(), builder.length()); 3 }
分析:對比StringBuffer入參,如果用StringBuilder作為入參是不加鎖操作的,因為StringBuilder本身線程不安全,但會提升性能,並且其源碼上也做出了相應注釋。
注意:以StringBuffer和StringBuilder為入參的構造函數這里要特別關注一下:StringBuffer線程安全,為了保證在String中也線程安全所以需要加鎖,而StringBuilder非線程安全,因此不需要加鎖操作,直接進行拷貝即可。
3.hashCode方法
1 public int hashCode() { 2 int h = hash; 3 if (h == 0 && value.length > 0) { 4 char val[] = value; 5 6 for (int i = 0; i < value.length; i++) { 7 h = 31 * h + val[i]; 8 } 9 hash = h; 10 } 11 return h; 12 }
分析:String的hashCode方法還是比較簡單的,它是遍歷char數組以31為基數做一個累加操作。注意這里是以31來作為的基數,為什么取31作為基數可參考:String hashCode 方法為什么選擇數字31作為乘子
4.equals方法
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 = value.length; 8 if (n == anotherString.value.length) { 9 char v1[] = value; 10 char v2[] = anotherString.value; 11 int i = 0; 12 while (n-- != 0) { 13 if (v1[i] != v2[i]) 14 return false; 15 i++; 16 } 17 return true; 18 } 19 } 20 return false; 21 }
分析:首先會判斷是否是同一個對象,如果是,則直接返回true;其次判斷傳入對象是否為String對象,如果對象不匹配直接返回false,否則依次比較兩個對象的char,只要發現一個不相等,則直接返回false,停止循環。這里的寫法還是比較簡潔的,在日常開發中可以利用起來。
5.關於不可變對象
關於不可變對象,這里看一段源碼就清楚了
1 public String substring(int beginIndex) { 2 if (beginIndex < 0) { 3 throw new StringIndexOutOfBoundsException(beginIndex); 4 } 5 int subLen = value.length - beginIndex; 6 if (subLen < 0) { 7 throw new StringIndexOutOfBoundsException(subLen); 8 } 9 return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); 10 }
截取函數,直接看第9行代碼處,如果beginIndex==0,返回的是當前對象,否則這里是new的一個新對象,其實String中的很多函數都是這樣的操作,具體可翻看源碼閱讀一下。
總結
#1.String類在日常開發中經常使用,但可能對其源碼並不是十分了解,通過分析其源碼,了解更多看似簡單的知識點。
#2.String類的構造函數非常多,需要注意一下,特別是StringBuffer和StringBuilder為入參的構造函數。
#3.String是不可變對象。
#4.String#hashCode的計算方式,以31為計算基數。
by Shawn Chen,2019.08.20日,上午。