小學徒成長系列—StringBuilder & StringBuffer關鍵源碼解析

在前面的博文《小學徒成長系列—String關鍵源碼解析》和《小學徒進階系列—JVM對String的處理》中,咱們講到了關於String的經常使用方法以及JVM對字符串常量String的處理。html

可是在Java中,關於字符串操做的類還有兩個,它們分別是StringBuilder和StringBuffer。咱們先來就講解一下String類和StringBuilder、StringBuffer的聯繫吧。java

String、StringBuilder、StringBuffer的異同點

結合以前寫的博文,咱們對這三個經常使用的類的異同點進行分析:面試

異:數組

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的構造方法類型是同樣的,裏面都是經過調用父類的構造方法進行實現的,在這裏,我主要以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經常使用的方法

在StringBuilder中,不少方法最終都是進行必定的邏輯處理,而後經過調用父類AbstractStringBuilder中的方法進行實現的。

 1>append(String str)

  從下面的代碼中咱們能夠看到,他是直接調用父類的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 }

  2> ensureCapacityInternal

  下面咱們看下,他每次拼接字符串的時候,是怎樣進行擴容的:

 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 }

  3>append(StringBuffer sb) 

  從這裏咱們能夠看到,它又是調用父類的方法進行拼接的。

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 }

  4>public StringBuilder delete(int start, int end) 

    刪除從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     }

  5>public StringBuilder replace(int start, int end, String str)

 關於這個方法,由於是直接調用父類中的方法進行實現的,因此咱們繼續直接看父類中的方法吧:

 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 }

  6>public StringBuilder insert(int offset, String str)

  在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 }

  7>indexOf(String str)

  其實這個的實現主要是藉助了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()方法是默認權限的,也就是隻有與他同包的狀況下才可以進行訪問這個方法。

  8> lastIndexOf()

  lastIndexOf()方法跟indexOf()差很少,調用了String.lastIndexOf()方法進行實現,再次不重複說明。

  9> public StringBuilder reverse()

     咱們常常進行字符串的逆轉,面試的時候也有常常問到,那麼實際上在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  * "&#92;uDC00&#92;uD800" produces "&#92;uD800&#92;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的經常使用方法

 前面咱們知道StringBuffer至關於StringBuilder來講是線程安全的,因此再StringBuffer中,全部的方法都加了同步synchronized,例如append(String str)方法:

public synchronized StringBuffer append(String str) {
    super.append(str);
    return this;
}

具體內部實現就不詳細說明啦,跟StringBuilder是同樣的,大部分都是調用AbstractStringBuilder進行實現的。

相關文章
相關標籤/搜索