StringBuilder原始碼分析,讓你不用再死記答案

lightwing發表於2021-09-09

1、前言

相信學習Java的小夥伴對String、StringBuilder、StringBuffer都不會陌生,幾乎每天都和它們打交道,但是你是否真的瞭解其它們呢?雖然看似簡單的基礎知識,但是確實高頻的面試點。很多同學背過相關面試題,但是很容易就會忘記,主要是沒有隻有背、少了理解,因此很容易就會給忘記了。

2、StringBuilder的簡單使用


StringBuilder sb=new StringBuilder();
sb.append("hello");
sb.append("world");
System.out.println(sb.toString());

是不是非常的熟悉,StringBuilder可以用來拼接字串。那麼大家有沒有相關它底層是如何實現的呢?

3、類的關係梳理

第一,定義一個介面


public interface CharSequence {
    //獲取內容長度
    int length();
    //定位某個字元
    char charAt(int index);
    //擷取字串
    CharSequence subSequence(int start, int end);
    //toString
    public String toString();
}

第二,定義一個抽象類

abstract class AbstractStringBuilder implements Appendable, CharSequence {
  //陣列字元的陣列
  char[] value;
  //字元長度
  int count;

  //建構函式(傳遞容量大小)
  AbstractStringBuilder(int capacity) {
    //初始化一個陣列
    value = new char[capacity];
  }

  //返回長度
  public int length() {
    return count;
  }
  //返回容量大小
  public int capacity() {
    return value.length;
  }
}

第三步,定義一個實現類

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
  //建構函式
  public StringBuilder() {
    //如果不指定容量值,則預設是16
    super(16);
  }
  
  //建構函式
  public StringBuilder(int capacity) {
    super(capacity);
  }
}

透過以上的簡單類結構,我們需要掌握以下幾個核心問題
①什麼需要加一個抽象類,AbstractStringBuilder呢?為什麼不直接實現類實現介面呢?
②StringBuilder底層是char[]陣列,它的資料結構是陣列,如果不指定則預設是16
③為什麼需要count欄位呢?length()方法和capacity()的區別是什麼呢?這不是多餘的嗎?

溫馨提示:我們看原始碼的時候,看的過程帶著問題、帶著疑問去原始碼當中尋找答案,這樣才能有所收穫,而不是為了看原始碼而去看原始碼。

一般來說,抽象類可以用來解耦介面和實現類,很多優秀的框架幾乎都是這種模式,好處是把公共的部分抽取到抽象類實現,減輕實現類的操作,具體如下所示。

//介面
public interface ITest{
  public void sayHello(String name);
}
//實現類
public class TestImpl implements ITest{
  @Override
  public void sayHello(String name){
    //對name進行校驗
    if(name!=null&&!"".equals(name)){
      //具體的業務處理
    }
  }
}

這種模式的缺點就是如果ITest有好多個實現類,每個實現類都需要對name欄位進行校驗。那麼如何最佳化呢?


//介面
public interface ITest{
  public void sayHello(String name);
}

//抽象類
public class AbstractTestImpl implements ITest{
  @Override
  public void sayHello(String name){
    //對name進行校驗
    if(name!=null&&!"".equals(name)){
      //呼叫抽象方法
      say(name);
    }
  }
  //定義一個抽象方法
  public abstract void say(String name);
}

//實現類
public class TestImpl extends AbstractTestImpl{
  public void say(String name){
    //無需要做校驗,直接開始處理業務即可
  }
}

看到這裡,是不是明白為什麼需要加AbstractStringBuilder類了呢?

4、StringBuilder儲存資料

AbstractStringBuilder類的append方法解析


public AbstractStringBuilder append(String str) {
        if (str == null){
            str = "null";
        }
        //1.獲取追加字串的長度
        int len = str.length();
        //2.動態擴容char[]陣列【陣列長度是count+len】【繼續看原始碼】
        ensureCapacityInternal(count + len);
        //3.往擴容char[]陣列新增內容
        str.getChars(0, len, value, count);
        //4.count屬性累加
        count += len;
        
        return this;
}

StringBuilder類的append方法解析

public StringBuilder append(String str) {
    //它本身不處理,只是呼叫父類的方法
    super.append(str);
    return this;
}

那麼如何擴容的呢?

private void ensureCapacityInternal(int minimumCapacity) {
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
    }
}
void expandCapacity(int minimumCapacity) {
    //1.新陣列的長度是原來的兩倍
    int newCapacity = value.length * 2 + 2;
    //2.判斷兩個值,最後覺得以哪個長度為主
    if (newCapacity - minimumCapacity < 0){
            newCapacity = minimumCapacity;
    }
    //3.判斷新陣列長度是否為小於0
    if (newCapacity < 0) {
        if (minimumCapacity < 0){
            throw new OutOfMemoryError();
        }
        //如果小於0則取Integer的最大值
        newCapacity = Integer.MAX_VALUE;
    }
    //4.複製陣列【繼續看原始碼】
    value = Arrays.copyOf(value, newCapacity);
}

思考:newCapacity為什麼會小於0呢?是不是很奇怪呢?
解析:因為int是32位的二進位制,最高位是符號位(0正數,1負數),如果newCapacity大於Integer最大值,那麼首位被擠掉了,由0變成1,那麼就變成了負數了。

public static char[] copyOf(char[] original, int newLength) {
    //1.建立一個新的陣列
    char[] copy = new char[newLength];
    //2.複製陣列【繼續看原始碼】
    System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
    return copy;
}

System.arraycopy是JVM底層提供的方法,native修飾的,用它來進行陣列之間的複製。
原始碼分析到這裡,我們必須掌握兩個核心的東西
第一)StringBuilder底層是char[]陣列
第二)StringBuilder是動態擴容的,它是透過建立一個新的陣列,然後把舊陣列的資料複製到新陣列當中,舊陣列給gc回收

5、StringBuilder刪除資料

public AbstractStringBuilder deleteCharAt(int index) {
    //1.校驗
    if ((index < 0) || (index >= count)){
        throw new StringIndexOutOfBoundsException(index);
    }
    
    //2.複製陣列【同一個陣列之間的複製】
    System.arraycopy(value, index+1, value, index, count-index-1);
    
    //3.count遞減
    count--;
    return this;
}

複製這個地方思路可能有點繞,給大家重點分析一下
char[] arrs={0,1,2,3,4,5,6},有7個元素,我們要刪除index=4的元素,那麼如何刪除呢?思路是arrs陣列直接的複製,
System.arraycopy(src,srcPos,dest,destPos,length)
①src表示源陣列
②srcPos表示從源陣列的什麼位置開始複製
③dest表示複製到哪個陣列
④destPos表示複製到新陣列的哪個位置
⑤length表示複製舊陣列的幾個元素

System.arraycopy(arrs,index+1,arrs,index,arrs.length-1-index)會執行如下操作
①0,1,2,3元素不變
②第5個元素放的是5
③第6個元素放的是6
④第七個元素是4,4會被擠到最後【大家可以測試該函式的使用】
最後結果是,char[] arrs={0,1,2,3,5,6,4}

4就是被我們刪除的元素,我們只能透過把它擠到最後,然後透過count–表示陣列的有效長度,那麼讀取陣列的時候不是

for(int i=0;i<arrs.length;i++),而是for(int i=0;i<count;i++)

因此,大家回過頭去看前面的問題,為什麼需要count欄位,length()和capacity()方法的區別,是不是就恍然大悟了。

6、總結

看到這裡StringBuilder的核心思路就講解完成了,它有很多的方法,大家可以自己去看,其實都不難,掌握核心思想即可。
第一)它底層是char[]陣列
第二)新增元素的時候,如何擴容陣列
第三)刪除元素的時候,又是如何處理的

慕課網專欄(架構思想之微服務+仿百度網盤原始碼):

感謝您的閱讀,希望您多多支援,這樣我在原創的道路上才能更加的有信心。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3407/viewspace-2825350/,如需轉載,請註明出處,否則將追究法律責任。

相關文章