在前面的博文《小學徒成長系列—String關鍵源碼解析》和《小學徒進階系列—JVM對String的處理》中,咱們講到了關於String的經常使用方法以及JVM對字符串常量String的處理。html
可是在Java中,關於字符串操做的類還有兩個,它們分別是StringBuilder和StringBuffer。咱們先來就講解一下String類和StringBuilder、StringBuffer的聯繫吧。java
結合以前寫的博文,咱們對這三個經常使用的類的異同點進行分析:面試
異:數組
1>String的對象是不可變的;而StringBuilder和StringBuffer是可變的。安全
2>StringBuilder不是線程安全的;而StringBuffer是線程安全的app
3>String中的offset,value,count都是被final修飾的不可修改的;而StringBuffer和StringBuilder中的value,count都是繼承自AbstractStringBuilder類的,沒有被final修飾,說明他們在運行期間是可修改的,並且沒有offset變量。less
同:ui
三個類都是被final修飾的,是不可被繼承的。this
其實StringBuilder和StringBuffer的構造方法類型是同樣的,裏面都是經過調用父類的構造方法進行實現的,在這裏,我主要以StringBuilder爲例子講解,StringBuffer就不重複累贅的講啦。spa
1>構建一個初始容量爲16的默認的字符串構建
1 public StringBuilder() { 2 super(16); 3 }
從構造方法中咱們看到,構造方法中調用的是父類AbstractStringBuilder中的構造方法,咱們來看看,父類中的構造方法:
1 /** 2 * 構造一個不帶任何字符的字符串生成器,其初始容量由 capacity 參數指定。 3 * @params capacity 數組初始化容量 4 */ 5 AbstractStringBuilder(int capacity) { 6 value = new char[capacity]; 7 }
這個構造方法說明的是,建立一個初始容量由 capacity 參數指定的字符數組,而子類中傳過來的是16,因此建立的就是初始容量爲16的字符數組
2>構造一個不帶任何字符的字符串生成器,其初始容量由 capacity 參數指定。
1 public StringBuilder(int capacity) { 2 super(capacity); 3 }
這個構造方法調用的跟上面1>的構造方法是同一個的,只是這裏子類中的初始化容量由用戶決定。
3>構造一個字符串生成器,並初始化爲指定的字符串內容。該字符串生成器的初始容量爲 16 加上字符串參數的長度。
1 public StringBuilder(String str) { 2 super(str.length() + 16); 3 append(str); 4 }
這個構造方法首先調用和1>同樣的父類構造方法,而後再調用本類中的append()方法將字符串str拼接到本對象已有的字符串以後。
4>構造一個字符串生成器,包含與指定的 CharSequence 相同的字符。該字符串生成器的初始容量爲 16 加上 CharSequence 參數的長度。
1 public StringBuilder(CharSequence seq) { 2 this(seq.length() + 16); 3 append(seq); 4 }
嗯,這個構造方法,你們一看就知道跟上面的差很少啦,我就不介紹啦。
在StringBuilder中,不少方法最終都是進行必定的邏輯處理,而後經過調用父類AbstractStringBuilder中的方法進行實現的。
從下面的代碼中咱們能夠看到,他是直接調用父類的append方法進行實現的。
1 public StringBuilder append(String str) { 2 super.append(str); 3 return this; 4 }
下面咱們再看下父類AbstractStringBuilder中的append方法是怎麼寫的
1 public AbstractStringBuilder append(String str) { 2 //注意,當str的值爲nul時,將會在當前字符串對象後面添加上Null字符串 3 if (str == null) str = "null"; 4 //獲取須要添加的字符串的長度 5 int len = str.length(); 6 //判斷添加後的字符串對象是否超過容量,如果,擴容 7 ensureCapacityInternal(count + len); 8 //將str中的字符串複製到value數組中 9 str.getChars(0, len, value, count); 10 //更新當前字符串對象的字符串長度 11 count += len; 12 return this; 13 }
下面咱們看下,他每次拼接字符串的時候,是怎樣進行擴容的:
1 /** 2 * This method has the same contract as ensureCapacity, but is 3 * never synchronized. 4 */ 5 private void ensureCapacityInternal(int minimumCapacity) { 6 // overflow-conscious code 7 // 若是須要擴展到的容量比當前字符數組長度要大 8 // 那麼就正常擴容 9 if (minimumCapacity - value.length > 0) 10 expandCapacity(minimumCapacity); 11 } 12 13 /** 14 * This implements the expansion semantics of ensureCapacity with no 15 * size check or synchronization. 16 */ 17 void expandCapacity(int minimumCapacity) { 18 // 初始化新的容量大小爲當前字符串長度的2倍加2 19 int newCapacity = value.length * 2 + 2; 20 // 若是新容量大小比傳進來的最小容量還要小 21 // 就是用最小的容量爲新數組的容量 22 if (newCapacity - minimumCapacity < 0) 23 newCapacity = minimumCapacity; 24 // 若是新的容量或者最小容量小於0 25 // 拋異常而且講新容量設置成Integer最能存儲的最大值 26 if (newCapacity < 0) { 27 if (minimumCapacity < 0) // overflow 28 throw new OutOfMemoryError(); 29 newCapacity = Integer.MAX_VALUE; 30 } 31 // 建立容量大小爲newCapacity的新數組 32 value = Arrays.copyOf(value, newCapacity); 33 }
從這裏咱們能夠看到,它又是調用父類的方法進行拼接的。
1 public StringBuilder append(StringBuffer sb) { 2 super.append(sb); 3 return this; 4 }
繼續看父類中的拼接方法:
1 // Documentation in subclasses because of synchro difference 2 public AbstractStringBuilder append(StringBuffer sb) { 3 // 若是sb的值爲null,這裏就會爲字符串添加上字符串「null」 4 if (sb == null) 5 return append("null"); 6 // 獲取須要拼接過來的字符串的長度 7 int len = sb.length(); 8 // 擴容當前兌現搞定字符數組容量 9 ensureCapacityInternal(count + len); 10 // 進行字符串的拼接 11 sb.getChars(0, len, value, count); 12 // 更新當前字符串對象的長度變量 13 count += len; 14 return this; 15 }
刪除從start開始到end結束的字符(包括start但不包括end)
1 public StringBuilder delete(int start, int end) { 2 super.delete(start, end); 3 return this; 4 }
是的,又是調用父類進行操做的。
1 /** 2 * Removes the characters in a substring of this sequence. 3 * The substring begins at the specified {@code start} and extends to 4 * the character at index {@code end - 1} or to the end of the 5 * sequence if no such character exists. If 6 * {@code start} is equal to {@code end}, no changes are made. 7 * 8 * @param start The beginning index, inclusive. 9 * @param end The ending index, exclusive. 10 * @return This object. 11 * @throws StringIndexOutOfBoundsException if {@code start} 12 * is negative, greater than {@code length()}, or 13 * greater than {@code end}. 14 */ 15 public AbstractStringBuilder delete(int start, int end) { 16 // 健壯性的檢查 17 if (start < 0) 18 throw new StringIndexOutOfBoundsException(start); 19 if (end > count) 20 end = count; 21 if (start > end) 22 throw new StringIndexOutOfBoundsException(); 23 // 須要刪除的長度 24 int len = end - start; 25 if (len > 0) { 26 // 進行復制,將被刪除的元素後面的複製到前面去 27 System.arraycopy(value, start+len, value, start, count-end); 28 // 更新字符串長度 29 count -= len; 30 } 31 return this; 32 }
其實看了那麼多,咱們也很容易發現,不論是String類仍是如今博文中的StringBuilder和StringBuffer,底層實現都用到了Arrays.copyOfRange(original, from, to);和System.arraycopy(src, srcPos, dest, destPos, length);這兩個方法實現的。
在看完上面那段源代碼以後,我忽然想到了一個問題,就是若是須要剩下的字符個數少於須要被覆蓋的字符個數時怎麼辦,看下面的代碼:
1 import java.util.Arrays; 2 3 public class StringBuilderTest { 4 public static void main(String[] args) { 5 char[] src = {'a', 'b', 'c', 'd', 'e', 'f', 'g'}; 6 int start = 4; 7 int end = 5; 8 int len = end - start; 9 if (len > 0) { 10 //進行復制,將被刪除 11 System.arraycopy(src, start+len, src, start, src.length-end); 12 } 13 System.out.println(src); 14 15 StringBuilder stringBuilder = new StringBuilder("abcdefg"); 16 stringBuilder.delete(4, 5); 17 System.out.println(stringBuilder); 18 } 19 }
結果輸出了:
奇怪,爲何StringBuilder能夠輸出abcdefg而個人會多了一個g呢?緣由是在StringBuilder中的toString方法中從新建立了一個有效數字爲count的,也就是說值爲abcdefg的字符串對象,以下代碼:
1 public String toString() { 2 // Create a copy, don't share the array 3 return new String(value, 0, count); 4 }
關於這個方法,由於是直接調用父類中的方法進行實現的,因此咱們繼續直接看父類中的方法吧:
1 /** 2 * Replaces the characters in a substring of this sequence 3 * with characters in the specified <code>String</code>. The substring 4 * begins at the specified <code>start</code> and extends to the character 5 * at index <code>end - 1</code> or to the end of the 6 * sequence if no such character exists. First the 7 * characters in the substring are removed and then the specified 8 * <code>String</code> is inserted at <code>start</code>. (This 9 * sequence will be lengthened to accommodate the 10 * specified String if necessary.) 11 * 12 * @param start The beginning index, inclusive. 13 * @param end The ending index, exclusive. 14 * @param str String that will replace previous contents. 15 * @return This object. 16 * @throws StringIndexOutOfBoundsException if <code>start</code> 17 * is negative, greater than <code>length()</code>, or 18 * greater than <code>end</code>. 19 */ 20 public AbstractStringBuilder replace(int start, int end, String str) { 21 // 健壯性的檢查 22 if (start < 0) 23 throw new StringIndexOutOfBoundsException(start); 24 if (start > count) 25 throw new StringIndexOutOfBoundsException("start > length()"); 26 if (start > end) 27 throw new StringIndexOutOfBoundsException("start > end"); 28 29 if (end > count) 30 end = count; 31 32 // 獲取須要添加的字符串的長度 33 int len = str.length(); 34 // 計算新字符串的長度 35 int newCount = count + len - (end - start); 36 // 對當前對象的數組容量進行擴容 37 ensureCapacityInternal(newCount); 38 // 進行數組的中的元素移位,從而空出足夠的空間來容納須要添加的字符串 39 System.arraycopy(value, end, value, start + len, count - end); 40 // 將str複製到value中 41 str.getChars(value, start); 42 // 更新字符串長度 43 count = newCount; 44 return this 45 }
在offset位置插入字符串str,他的實現也是經過父類進行實現的,繼續看父類中的相應方法:
1 /** 2 * Inserts the string into this character sequence. 3 * <p> 4 * The characters of the {@code String} argument are inserted, in 5 * order, into this sequence at the indicated offset, moving up any 6 * characters originally above that position and increasing the length 7 * of this sequence by the length of the argument. If 8 * {@code str} is {@code null}, then the four characters 9 * {@code "null"} are inserted into this sequence. 10 * <p> 11 * The character at index <i>k</i> in the new character sequence is 12 * equal to: 13 * <ul> 14 * <li>the character at index <i>k</i> in the old character sequence, if 15 * <i>k</i> is less than {@code offset} 16 * <li>the character at index <i>k</i>{@code -offset} in the 17 * argument {@code str}, if <i>k</i> is not less than 18 * {@code offset} but is less than {@code offset+str.length()} 19 * <li>the character at index <i>k</i>{@code -str.length()} in the 20 * old character sequence, if <i>k</i> is not less than 21 * {@code offset+str.length()} 22 * </ul><p> 23 * The {@code offset} argument must be greater than or equal to 24 * {@code 0}, and less than or equal to the {@linkplain #length() length} 25 * of this sequence. 26 * 27 * @param offset the offset. 28 * @param str a string. 29 * @return a reference to this object. 30 * @throws StringIndexOutOfBoundsException if the offset is invalid. 31 */ 32 public AbstractStringBuilder insert(int offset, String str) { 33 if ((offset < 0) || (offset > length())) 34 throw new StringIndexOutOfBoundsException(offset); 35 if (str == null) 36 str = "null"; 37 int len = str.length(); 38 ensureCapacityInternal(count + len); 39 // 將字符串後移爲插入的字符串留充足的空間 40 System.arraycopy(value, offset, value, offset + len, count - offset); 41 // 將str複製到value數組中 42 str.getChars(value, offset); 43 // 更新當前對象中記錄的長度 44 count += len; 45 return this; 46 }
其實這個的實現主要是藉助了String對象的indexOf方法來實現的,具體能夠參考博文:http://www.cnblogs.com/xiaoxuetu/archive/2013/06/05/3118229.html 這裏就不詳細進行講解了:
1 /** 2 * @throws NullPointerException {@inheritDoc} 3 */ 4 public int indexOf(String str) { 5 return indexOf(str, 0); 6 }
調用了同一個類中的indexOf方法:
1 /** 2 * @throws NullPointerException {@inheritDoc} 3 */ 4 public int indexOf(String str, int fromIndex) { 5 //調用了String類中的靜態方法indexOf 6 return String.indexOf(value, 0, count, 7 str.toCharArray(), 0, str.length(), fromIndex); 8 }
String.indexOf()方法是默認權限的,也就是隻有與他同包的狀況下才可以進行訪問這個方法。
lastIndexOf()方法跟indexOf()差很少,調用了String.lastIndexOf()方法進行實現,再次不重複說明。
咱們常常進行字符串的逆轉,面試的時候也有常常問到,那麼實際上在jdk中式怎麼完成這些操做的呢?
首先咱們看下StringBuilder中的reverse方法():
1 public StringBuilder reverse() { 2 //調用父類的reverse方法 3 super.reverse(); 4 return this; 5 }
通常狀況下,若是讓咱們來進行逆轉,會怎麼寫呢?我想不少人都會像下面那樣子寫吧:
1 public String reverse(char[] value){ 2 //折半,從中間開始置換 3 for (int i = (value.length - 1) >> 1; i >= 0; i--){ 4 char temp = value[i]; 5 value[i] = value[value.length - 1 - i]; 6 value[value.length - 1 - i] = temp; 7 } 8 return new String(value); 9 }
確實很簡單,可是一個完整的 Unicode 字符叫代碼點CodePoint,而一個 Java char 叫 代碼單元 code unit。若是String 對象以UTF-16保存 Unicode 字符,須要用2個字符表示一個超大字符集的漢字,這這種表示方式稱之爲 Surrogate,第一個字符叫 Surrogate High,第二個就是 Surrogate Low。所在在JDK中也加入了判斷一個char是不是Surrogate區的字符:
1 /** 2 * Causes this character sequence to be replaced by the reverse of 3 * the sequence. If there are any surrogate pairs included in the 4 * sequence, these are treated as single characters for the 5 * reverse operation. Thus, the order of the high-low surrogates 6 * is never reversed. 7 * 8 * Let <i>n</i> be the character length of this character sequence 9 * (not the length in <code>char</code> values) just prior to 10 * execution of the <code>reverse</code> method. Then the 11 * character at index <i>k</i> in the new character sequence is 12 * equal to the character at index <i>n-k-1</i> in the old 13 * character sequence. 14 * 15 * <p>Note that the reverse operation may result in producing 16 * surrogate pairs that were unpaired low-surrogates and 17 * high-surrogates before the operation. For example, reversing 18 * "\uDC00\uD800" produces "\uD800\uDC00" which is 19 * a valid surrogate pair. 20 * 21 * @return a reference to this object. 22 */ 23 public AbstractStringBuilder reverse() { 24 // 默認沒有存儲到Surrogate區的字符 25 boolean hasSurrogate = false; 26 int n = count - 1; 27 // 折半,遍歷而且首尾相應位置置換 28 for (int j = (n-1) >> 1; j >= 0; --j) { 29 char temp = value[j]; 30 char temp2 = value[n - j]; 31 if (!hasSurrogate) { 32 // 判斷一個char是不是Surrogate區的字符 33 hasSurrogate = (temp >= Character.MIN_SURROGATE && temp <= Character.MAX_SURROGATE) 34 || (temp2 >= Character.MIN_SURROGATE && temp2 <= Character.MAX_SURROGATE); 35 } 36 // 首尾值置換 37 value[j] = temp2; 38 value[n - j] = temp; 39 } 40 41 // 若是含有Surrogate區的字符 42 if (hasSurrogate) { 43 // Reverse back all valid surrogate pairs 44 for (int i = 0; i < count - 1; i++) { 45 char c2 = value[i]; 46 if (Character.isLowSurrogate(c2)) { 47 char c1 = value[i + 1]; 48 if (Character.isHighSurrogate(c1)) { 49 //下面這行代碼至關於 50 //value[i]=c1; i=i+1; 51 value[i++] = c1; 52 value[i] = c2; 53 } 54 } 55 } 56 } 57 return this; 58 }
前面咱們知道StringBuffer至關於StringBuilder來講是線程安全的,因此再StringBuffer中,全部的方法都加了同步synchronized,例如append(String str)方法:
public synchronized StringBuffer append(String str) { super.append(str); return this; }
具體內部實現就不詳細說明啦,跟StringBuilder是同樣的,大部分都是調用AbstractStringBuilder進行實現的。