天天看點

深入源碼分析StringBuffer和StringBuilder深入源碼分析StringBuffer和StringBuilder

深入源碼分析StringBuffer和StringBuilder

衆所周知,StringBuffer是線程安全,StringBuilder線程不安全,是以StringBuilder性能略高,那還有沒有其他細節上的特性呢?讓我們從源碼分析

StringBuffer和StringBuilder都繼承了AbstractStringBuilder類

AbstractStringBuilder類關鍵源碼

  • 抽象類,是Stringbuilder和StringBuffer的父類,有兩個主要的屬性:
    • 字元數組value,用來存放字元串
    • int類型的count值,用于

      計算存儲使用的字元數量

      ,并且每次增删改都會更新該count值
    • capacity方法才是傳回字元數組的總長度,value.length;
  • 該類中的大部分方法被其子類StringBuffer和StringBuilder所調用,其中的擴容機制是關鍵
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    //用來存字元串
    char[] value;
    //用來計算存儲使用的字元數量
    int count;
    public int length() { 
        //注意,這裡的length方法傳回的是count的值,而不是value.length,
        //差別于String類中的length方法 
        return count;  
    }  
   
    //capacity()才是傳回字元數組的總長度
    public int capacity() {
        return value.length;
    }
                

ensureCapacity擴容機制

//擴容機制 確定容量
     public void ensureCapacity(int minimumCapacity) {
        // 最小容量肯定大于0,嚴謹性判斷
        if (minimumCapacity > 0)
            // 一層一層環環相扣的封裝
            ensureCapacityInternal(minimumCapacity);
    }
    
    private void ensureCapacityInternal(int minimumCapacity) {
        //如果最小容量大于内置的數組長度就必須要擴容了
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }
    //開始擴容
    void expandCapacity(int minimumCapacity) {
        //自動擴容機制,每次擴容(value.length+1)*2 
        int newCapacity = value.length * 2 + 2; 
        //如果擴容後的容量還是小于傳入的參數,就直接将傳入的參數設為容量值
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        //如果傳入的參數小于0,則直接把容量設定到Integer的最大值 2^31-1 
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        //還是使用了Arrays.copyOf函數直接傳回一個新容量的數組
        value = Arrays.copyOf(value, newCapacity);
    }
                
//用于保留value的值,保留的長度為count的值,隻有當count的值小于value.length時起作用
    public void trimToSize() {
        //當count小于value.length時,将value多餘長度的值删除,
        //此時value.length的長度等于count
        if (count < value.length) {
            value = Arrays.copyOf(value, count);
        }
    }
    // 設定長度length
    public void setLength(int newLength) {
        if (newLength < 0)
            throw new StringIndexOutOfBoundsException(newLength);
        //當傳入的值大于等于0時,進行擴容
        ensureCapacityInternal(newLength);
        
		//當傳入值大于字元統計量
        if (count < newLength) { 
            //為新擴容的數組中的新元素填充'\0',同樣也是結束符 從count位置到newLength位置
            Arrays.fill(value, count, newLength, '\0');  
        }
        //最後設定新的字元長度量
        count = newLength;
    }
    
    //append方法使用到的一個方法,用以添加一個字元串數組
    public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
    {
        if (srcBegin < 0)
            throw new StringIndexOutOfBoundsException(srcBegin);
        if ((srcEnd < 0) || (srcEnd > count))
            throw new StringIndexOutOfBoundsException(srcEnd);
        if (srcBegin > srcEnd)
            throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
        //用于添加字元串,将value的值添加到dst[]中  
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }
                

append方法

//append方法的重載
    //在對象後拼接字元串或其他對象,效率相比String較高,
    //因其在拼接時并沒有建立新的對象,不會造成大量對象的堆積,性能提升相當明顯
    public AbstractStringBuilder append(Object obj) {
       	return append(String.valueOf(obj));
    }
    
    public AbstractStringBuilder append(String str) {
        if (str == null)
            //若傳入的字元串為null,則預設添加“null”這個字元串
            return appendNull();
       
        int len = str.length();
        //注意擴容,直接是計算字元量的長度,根據長度就擴容到這麼多
        ensureCapacityInternal(count + len);
        //用getChars方法去添加字元串數組
        str.getChars(0, len, value, count);
        //更新count字元量值,每次添加就會加上新加入的字元串長度值
        count += len;
        return this;
    }
    
     //拼接StringBuffer對象,方法同理
     public AbstractStringBuilder append(StringBuffer sb) {
        if (sb == null)
            return appendNull();
        int len = sb.length();
        ensureCapacityInternal(count + len);
        sb.getChars(0, len, value, count);
        count += len;
        return this;
    }
    //同樣也可以拼接AbstractStringBuilder對象,也可以是其實作類,例如StringBuilder
     AbstractStringBuilder append(AbstractStringBuilder asb) {
        if (asb == null)
            return appendNull();
        int len = asb.length();
        ensureCapacityInternal(count + len);
        asb.getChars(0, len, value, count);
        count += len;
        return this;
    }
    
     //對象為null時,就添加個“null”字元串
     private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }
    
     //添加int類型的值
     public AbstractStringBuilder append(int i) {
        //細節,當int值是最小值時,特殊處理
        if (i == Integer.MIN_VALUE) {
            append("-2147483648");
            return this;
        }
        //細節,判斷Integer的位數,負數有負号,要多加一位
        int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1
                                     : Integer.stringSize(i);
        //根據新傳入的int值,确定總體字元串長度值,根據此時長度值進行擴容
        int spaceNeeded = count + appendedLength;
        //擴容
        ensureCapacityInternal(spaceNeeded);
        //擴容之後就是添加值了,int值用Integer的靜态方法
        Integer.getChars(i, spaceNeeded, value);
        //更新總體的字元數組長度值
        count = spaceNeeded;
        return this;
    }
    //添加long類型的值,原理和int類似
    public AbstractStringBuilder append(long l) {
        if (l == Long.MIN_VALUE) {
            append("-9223372036854775808");
            return this;
        }
        int appendedLength = (l < 0) ? Long.stringSize(-l) + 1
                                     : Long.stringSize(l);
        int spaceNeeded = count + appendedLength;
        ensureCapacityInternal(spaceNeeded);
        Long.getChars(l, spaceNeeded, value);
        count = spaceNeeded;
        return this;
    }
   	//浮點類型,直接調用對應的包裝類中的方法
    public AbstractStringBuilder append(float f) {
        FloatingDecimal.appendTo(f,this);
        return this;
    }
    
     public AbstractStringBuilder append(double d) {
        FloatingDecimal.appendTo(d,this);
        return this;
    }
    
    //添加Boolean類型 簡單粗暴
    public AbstractStringBuilder append(boolean b) {
        if (b) {
            ensureCapacityInternal(count + 4);
            value[count++] = 't';
            value[count++] = 'r';
            value[count++] = 'u';
            value[count++] = 'e';
        } else {
            ensureCapacityInternal(count + 5);
            value[count++] = 'f';
            value[count++] = 'a';
            value[count++] = 'l';
            value[count++] = 's';
            value[count++] = 'e';
        }
        return this;
    }
                

delete方法

//删除一部分的字元,傳入要删除段的開始下标和終止下标
    public AbstractStringBuilder delete(int start, int end) {
     	//驗證參數的有效性  
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            //結束下标大于count時,将count設為結束下标 
            end = count; 
        if (start > end)
            //開始下标就大于結束下标
            throw new StringIndexOutOfBoundsException();
        //要删除的長度值
        int len = end - start;
        if (len > 0) {
            
            //System的靜态方法來實作數組之間的複制。
            //src:源數組;
            //srcPos:源數組要複制的起始位置;
            //dest:目的數組;
            //destPos:目的數組放置的起始位置;
            //length:複制的長度。注意:src and dest都必須是同類型或者可以進行轉換類型的數組.
            
        	//System.arraycopy函數可以實作自己到自己複制
            //将中間要删除段省略,不進行複制 
            System.arraycopy(value, start+len, value, start, count-end);
            //更新count大小 
            count -= len;
        }
        return this;
    }
                

insert方法

//在對象中間插入字元串數組
    public AbstractStringBuilder insert(int dstOffset, CharSequence s) {
  
        if (s == null)
            s = "null";
        //明确了是String類型,會發生強制轉換
        if (s instanceof String)
            return this.insert(dstOffset, (String)s);
        return this.insert(dstOffset, s, 0, s.length());
    }
    
    //傳入的是插入開始下标,以及要插入的字元串
    public AbstractStringBuilder insert(int offset, String str) {
    
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null)
            str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);//先擴容
        //再複制數組,注意,此時隻是在value中建立起用于存放插入值的空位   
        System.arraycopy(value, offset, value, offset + len, count - offset);
        //向空位中插入str
        str.getChars(value, offset);
        //更新count值
        count += len;  
        return this;
    }
                

reverse方法

//反轉字元串
    public AbstractStringBuilder reverse() {
        boolean hasSurrogates = false;
        int n = count - 1;
        //采用從中間向兩端周遊,對換對稱位置上的字元  
        for (int j = (n-1) >> 1; j >= 0; j--) {
            int k = n - j;
            char cj = value[j];//兩個暫存變量
            char ck = value[k];
            value[j] = ck;//直接對應位置交換
            value[k] = cj;
            //驗證每個字元的編碼是否在範圍内
            if (Character.isSurrogate(cj) ||
                Character.isSurrogate(ck)) {
                hasSurrogates = true;
            }
        }
        if (hasSurrogates) {
        	//直接反轉後,如果兩字元順序錯了,就需要重新調整。
        	//考慮到存在增補字元,需要成對校驗,就是超出了字元的編碼範圍的話就會重新翻轉到原來那樣
            reverseAllValidSurrogatePairs();
        }
        return this;
    }
    
    //reverse的依賴方法,重新調整字元順序
    private void reverseAllValidSurrogatePairs() {
        for (int i = 0; i < count - 1; i++) {
            char c2 = value[i];
            if (Character.isLowSurrogate(c2)) {
                char c1 = value[i + 1];
                if (Character.isHighSurrogate(c1)) {
                    value[i++] = c1;
                    value[i] = c2;
                }
            }
        }
    }

                

substring方法

//傳回一個新字元串,是此字元串的一個子字元串
    public String substring(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            throw new StringIndexOutOfBoundsException(end);
        if (start > end)
            throw new StringIndexOutOfBoundsException(end - start);
        //注意是傳回了一個新建立的字元串對象
        return new String(value, start, end - start);
    }
}
                

System.arraycopy

Arrays.copyOf()方法傳回的數組是新的數組對象,數組拷貝時調用的是本地方法 System.arraycopy() ,原數組對象仍是原數組對象,不變,該拷貝不會影響原來的數組。

System.arraycopy() 源碼如下:

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
                

參數說明:

src:源對象

srcPos:源數組中的起始位置

dest:目标數組對象

destPos:目标資料中的起始位置

length:要拷貝的數組元素的數量

StringBuilder的關鍵源碼

  • 線程不安全,修改的方法全部都為線程不安全,是犧牲了安全用以實作性能
  • 由final修飾,不能被繼承,并且繼承了AbstractStringBuilder類
  • 重寫了toString方法
  • 實作了序列化接口,可序列化
  • 預設初始化容量為capacity = 16 ,基本所有jdk中實作類涉及初始化容量的大小都為16,加上一點擴容機制
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{
	 //對象序列化和反序列化需要的唯一辨別版本号
     static final long serialVersionUID = 4383685877147921099L;
     //預設構造方法
     public StringBuilder() {
      //使用父類的構造方法,預設初始化容量為capacity = 16 
             super(16);
      }
     //帶一個參數的構造方法,可以指定初始化容量
     public StringBuilder(int capacity) {
        super(capacity);
      }
     //帶一個參數構造方法,與前一個不同,這是指定一個String來初始化
     public StringBuffer(String str) {
        //注意,String初始化StringBuffer的時候,指定容量大小為String的長度加上16
        super(str.length() + 16);
        //追加到value中
        append(str);
    }
  
    
    @Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    @Override
    public String toString() {
        // Create a copy, don't share the array
        // 注意,此時是new了一個String對象,傳回了該String對象
        return new String(value, 0, count);
    }
    //将對象序列化,寫入了count和value。
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        s.defaultWriteObject();
        s.writeInt(count);
        s.writeObject(value);
    }
    
    //用于反序列化,将count和value屬性讀取出來
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        count = s.readInt();
        value = (char[]) s.readObject();
    }
}
                

StringBuffer的關鍵源碼

一個字元序列可變的字元串,這個StringBuffer提供了一系列的修改方法去改變字元串對象序列

  • 線程安全,增删改操作方法都加了

    synchronized

    鎖,加鎖系統開銷大,效率相對StringBuilder較低
  • 注意:和Builder的差別有一個

    transient char[] toStringCache

    ,toStringCache的字元數組,最後一次修改後的緩存值(字元數組儲存),隻要修改了value,那麼就會重置,當然

    這個Cache是建立線上程安全之上

    的,不保證線程安全就不會涉及到該Cache的操作,

    該toStringCache會在使用toString方法時起到提高效率的作用

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    // 注意:此屬性Builder中沒有,意圖是最後一次修改後的緩存值(字元數組儲存),
    // 隻要修改了value,那麼就會重置
    // transient 用于指定哪個字段不被預設序列化
    private transient char[] toStringCache;
    
    static final long serialVersionUID = 3388685877147921107L;
 
     public StringBuffer() {
        super(16);
    }
     public StringBuffer(int capacity) {
        super(capacity);
    }
    // 擷取字元串字元數量,加synchronized鎖
    @Override
    public synchronized int length() {
        return count;
    }
    // 擷取容量,效率低--對象鎖
    @Override
    public synchronized int capacity() {
        return value.length;
    }
    // 確定容量
    @Override
    public synchronized void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > value.length) {
        // 當最小容量值(傳進來的參數值)大于value.length(這個其實就是容量),那麼就直接擴容
            expandCapacity(minimumCapacity);
        }
    }
    //擴容,和AbstractStringBuilder父類中方法一緻
    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) 
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }
  
    @Override
    public synchronized void trimToSize() {
    	//直接調用父類的方法,但線程安全
        super.trimToSize();
    }
    //也是線程安全
    @Override
    public synchronized void setLength(int newLength) {
        toStringCache = null;
         //調用父類函數,擴充字元串容量到newLength,并且用空格填充
        super.setLength(newLength);
    }
    //根據指定索引擷取字元,效率慢,加鎖
    @Override
    public synchronized char charAt(int index) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        return value[index];
    }
    
     //根據索引修改字元串中某個字元值 
    @Override
    public synchronized void setCharAt(int index, char ch) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        //注意清除了緩存,隻要修改了value,此值就會置空   
        toStringCache = null;
        value[index] = ch;
    }
    
    //差別在于加鎖了以及清除了緩存,隻要修改了value,此值就會置空   
    @Override
    public synchronized StringBuffer append(Object obj) { 	
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }
    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    
    
    //不存在記憶體洩漏,實作了線程安全 
    @Override
    public synchronized String substring(int start) {
        return substring(start, count);
    }
    
    @Override
    public synchronized String substring(int start, int end) {
        return super.substring(start, end);
    }
    

    //有一套這樣的insert方法,分為同步與不同步方法
    //隻有在參數為char類型時,才是同步方法
    @Override
    public  StringBuffer insert(int offset, boolean b) {
    	//此方法不同步, 而且也沒有 toStringCache = null;
        super.insert(offset, b);
        return this;
    }
    @Override
    public synchronized StringBuffer insert(int offset, char c) {
        toStringCache = null;
        super.insert(offset, c);
        return this;
    }
    

    @Override
    public synchronized String toString() {
    	//toStringCache是提高toString方法的效率,不用每次都是調用,做了一個緩存
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        //直接使用的toStringCache數組緩存,提高了效率
        return new String(toStringCache, true);//傳回一個新的string對象過去
    }
    
    // 自定義序列化字段
    //serialPersistentFields 用于指定哪些字段需要被預設序列化.如下:
    private static final java.io.ObjectStreamField[] serialPersistentFields =
    {
        new java.io.ObjectStreamField("value", char[].class),
        new java.io.ObjectStreamField("count", Integer.TYPE),
        new java.io.ObjectStreamField("shared", Boolean.TYPE),
    };

    // 序列化大到ObjectOutputStream,寫入了count和value、shared
    private synchronized void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        java.io.ObjectOutputStream.PutField fields = s.putFields();
        fields.put("value", value);
        fields.put("count", count);
        fields.put("shared", false);//預設shared為false
        s.writeFields();
    }

    //反序列化到對象,讀出count和value
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        java.io.ObjectInputStream.GetField fields = s.readFields();
        value = (char[])fields.get("value", null);
        count = fields.get("count", 0);
    }

}
                

字元串系列類的一些問題

效率問題

效率:StringBuilder > StringBuffer > String

  • StringBuffer和StringBuilder都有

    自動擴容

    機制,增删改操作時,傳回的是原來自身的對象,不會重新建立對象,不會産生很多對象垃圾
  • String有兩大缺點:
    • 傳回對象使用大量new操作,傳回值均為建立的String對象,會在堆記憶體中産生很多垃圾;
    • 雖然最終調用的是系統複制數組操作,但調用之前開銷非常大,隻能靠複制來解決拼接問題。
public static void main(String[] args) {
        long start = System.currentTimeMillis();
        String str = null;
        for (int i = 0; i < 20000; i++) {
            str = str + i + ",";
        }
        System.out.println("String耗時  "+ (System.currentTimeMillis() - start));
        System.out.println("-------------------");


        start = System.currentTimeMillis();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < 20000; i++) {
            buffer.append(i + ",");//線程安全是以慢一點,但前提這裡是單線程
        }
        System.out.println("StringBuffer耗時  "+(System.currentTimeMillis() - start));
        System.out.println("-------------------");
        start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 20000; i++) {
            builder.append(i + ",");//線程不安全,效率最高
        }
        System.out.println("StringBuilder耗時  "+(System.currentTimeMillis() - start));
    }
/*    
String耗時 2030
-------------------
StringBuffer耗時 5
-------------------
StringBuilder耗時 3
*
                

線程安全問題

  • 增删改操作時,StringBuffer線程安全,StringBuilder線程不安全

使用總結

  1. 如果要操作少量的String資料可以使用用 String
  2. 不考慮線程安全問題,大量資料進行操作使用 StringBuilder
  3. 考慮線程安全問題,大量資料進行操作使用 StringBuffer

繼續閱讀