java基礎中String、StringBuffer和StringBuilder的實現原理和區別

一.簡介:

  • 用來處理字符串常用的類有3種:String、StringBuffer和StringBuilder
  • String是不可變字符串,字符串的值一旦確定,則不可修改(指內存種的值不可修改)
    • 頻繁對String進行修改,會在內存種產生很多對象,垃圾數據,特別是當內存種引用的對象多了以後,JVM的GC機制就會開始工作,性能就會降低
    • String類種提供了很多方法,但沒有直接增刪改等方法
  • 如果在程序種對字符串進行頻繁修改,建議使用StringBufferStringBuilder

二.String、StringBuffer和StringBuilder的區別

1.String

  • 不可變類,屬性value爲不可變數組,定義多少,String中字符數組的長度就是多少,不存在字符數組拓容一說。
  • String類源碼
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
  • final修飾的String類,以及final修飾的char[] value表示String類不可被繼承,不能有子類,且value只能被初始化一次。這裏的value變量其實就是存儲了String字符串中的所有字符。
  • 那既然String不可變。我們再看一下String類的截取字符串的方法subString()
  • 源碼:
    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
  • 可以看到,在subString方法中,如果傳入放入參數爲0,就返回自身原對象,否則就是重新創建一個新的對象
  • 類似的可以看到,String類中的concat方法,replace方法,都是內部重新生成一個String對象
  • 這就是爲什麼我們採用String對象頻繁的進行拼接,截取,替換操作效率很低下的原因。

2.StringBuffer

  • 內部可變數組,存在初始化StringBuilder對象中字符數組容量爲16存在拓容
  • StringBuffer類源碼:
 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

    /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    static final long serialVersionUID = 3388685877147921107L;

    /**
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuffer() {
        super(16);
    }

    /**
     * Constructs a string buffer with no characters in it and
     * the specified initial capacity.
     *
     * @param      capacity  the initial capacity.
     * @exception  NegativeArraySizeException  if the {@code capacity}
     *               argument is less than {@code 0}.
     */
    public StringBuffer(int capacity) {
        super(capacity);
    }

    /**
     * Constructs a string buffer initialized to the contents of the
     * specified string. The initial capacity of the string buffer is
     * {@code 16} plus the length of the string argument.
     *
     * @param   str   the initial contents of the buffer.
     */
    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
  • 抽象類AbstractStringBuilder源碼:
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
  • StringBuffer類繼承AbstractStringBuilder抽象類,其中StringBuffer的大部分方法都是直接調用的是父類的實現
  • 第一個空參數的構造方法,調用父類(AbstractStringBuilder)的構造,默認字符數組的初始大小爲16
  • 第二個自定義初始char[] 容量大小
  • 第三個以字符串String作爲參數的構造。
  • 第四個在參數str數組長度的基礎上再增加16個字符長度,作爲StringBuffer實例的初始化數組容量,並將str字符串append到StringBuffer的數組中

注意:
可以看到這裏append方法用synchronized(同步) 修飾了,加了同步鎖,來實現多線程下的線程安全
其他的和StringBuilder一致

  • 具體看下父類AbstractStringBuilder的append方法
  • 源碼:
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

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


    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
  • 示例:
public class StringBuffer的append測試 {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        String s = null;    //沒有分配空間
        String s2 = "ccc";
        sb.append("aaa");
        sb.append(s);
        sb.append(s2);
        sb.append("bbb");
        System.out.println(sb);//aaanullcccbbb
    }

}

  • append方法
    • 該方法的作用是追加內容到當前的StringBuffer對象的末尾,類似於字符串的連接,調用該方法以後,StringBuffer對象的內容也發生改變
    • 使用該方法繼續寧字符串的連接,將比String更加節約內容,經常應用於數據庫SQL語句的連接,比如項目中dao層寫的SQL語句用append方法進行追加
  • 可以看到,首先判斷append的參數str是否爲null,如果爲null的話,這裏也是可以append進去的
  • 其中ensureCapacityInternal方法是確保這次append的時候StringBuffer的內部數組容量是滿足的,即這次要append的null字符長度爲4,加上之前內部數組中已有的字符位數c之後作爲參數執行。
  • 如果不爲null的話,就獲取這次需要append的str字符長度。緊接着執行是否需要拓容的方法
  • 即整個StringBuffer的append方法,直接將String類型的str字符串中的字符數組,拷貝到了StringBuffer的字符數組中
  • 圖示:
    字符串拷貝到StringBuffer中
    /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;
  • 這裏比StringBuilder多一個參數toStringCache,就是去緩存toString的,可以看下StringBuffer的toString方法
    @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }
  • 這裏的作用就是如果StringBuffer對象此時存在toStringCache,在多次調用其toString方法時,其new出來的String對象是會共享同一個char[]內存的,達到共享的目的。但是StringBuffer只要做了修改,其toStringCache屬性都會置null處理。這也是StringBuffer和StringBuilder的一個區別

3.StringBuilder

  • 其他的都是一樣,不一樣的地方有append方法前面沒有synchronized(同步)修飾
  • 還有一個不一樣的是StringBuilder的toString方法
  • 源碼:
    @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }
  • 這裏的toString方法直接new一個String對象,將StringBuilder對象的value進行一個拷貝,重新生成一個對象,不共享之前的StringBuilder的char[]

三.String、StringBuffer和StringBuilder性能對比

public class String和StringBuffer和StringBuilder性能對比 {
    public static void main(String[] args) {
        test_String();
        test_StringBuffer();
        test_StringBuilder();
    }

    public static void test_String(){
        String str = "";
        long start = System.currentTimeMillis();
        for (int i = 0;i < 10000; i++){
            str += "hello";
        }
        long end = System.currentTimeMillis();
        System.out.println("String花費了"+(end-start)+"毫秒");
    }

    public static void test_StringBuffer(){
        StringBuffer sb = new StringBuffer();
        long start = System.currentTimeMillis();
        for (int i = 0;i < 10000; i++){
            sb.append("hello");
        }
        long end = System.currentTimeMillis();
        System.out.println("StringBuffer花費了"+(end-start)+"毫秒");
    }

    public static void test_StringBuilder(){
        StringBuilder sb = new StringBuilder();
        long start = System.currentTimeMillis();
        for (int i = 0;i < 10000; i++){
            sb.append("hello");
        }
        long end = System.currentTimeMillis();
        System.out.println("StringBuilder花費了"+(end-start)+"毫秒");
    }
}

  • 輸出爲:
String花費了442毫秒
StringBuffer花費了2毫秒
StringBuilder花費了0毫秒
  • 從上面的結果可以看出:不考慮多線程,採用String對象,執行時間比其他兩個都要高,而採用StringBuffer對象和採用StringBuilder對象的差別也比較明顯。由此可見,如果我們的程序員是在單線程下運行,或者是不必考慮到線程同步問題,我們應該優先使用StringBuilder類。如果要保證線程安全,或者在現實的模塊化編程中,負責某一模塊的程序員不一定能清晰的判斷該模塊是否會放入多線程的環境中運行,自然是StringBuffer

四.總結:

  • StringBuffer
    • 線程安全的(同步–如果多個線程同時訪問 — 排隊一個一個訪問),多用於多線程
    • 效率低
  • StringBuilder
    • 線程不安全(異步–如果多個線程同時訪問 — 一起訪問),多用於單線程
    • 效率高

如果對你有幫助,點個贊吧0.0
若有不正之處,請多多諒解並歡迎批評指正,不甚感激

參考資料:

發佈了24 篇原創文章 · 獲贊 12 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章