JAVA面試題 StringBuffer和StringBuilder的區別,從原始碼角度分析?

Java螞蟻發表於2019-07-22

面試官Q1:請問StringBuffer和StringBuilder有什麼區別?

這是一個老生常談的話題,筆者前幾年每次面試都會被問到,作為基礎面試題,被問到的概率百分之八九十。下面我們從面試需要答到的幾個知識點來總結一下兩者的區別有哪些?

  • 繼承關係?

  • 如何實現的擴容?

  • 執行緒安全性?

 

繼承關係

從原始碼上看看類StringBuffer和StringBuilder的繼承結構:

 

從結構圖上可以直到,StringBuffer和StringBuiler都繼承自AbstractStringBuilder類

 

如何實現擴容

StringBuffer和StringBuiler的擴容的機制在抽象類AbstractStringBuilder中實現,當發現長度不夠的時候(預設長度是16),會自動進行擴容工作,擴充套件為原陣列長度的2倍加2,建立一個新的陣列,並將陣列的資料複製到新陣列。

public void ensureCapacity(int minimumCapacity) {
    if (minimumCapacity > 0)
        ensureCapacityInternal(minimumCapacity);
}

/**
* 確保value字元陣列不會越界.重新new一個陣列,引用指向value
*/    
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

/**
* 擴容:將長度擴充套件到之前大小的2倍+2
*/    
private int newCapacity(int minCapacity) {
    // overflow-conscious code   擴大2倍+2
    //這裡可能會溢位,溢位後是負數哈,注意
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    //MAX_ARRAY_SIZE的值是Integer.MAX_VALUE - 8,先判斷一下預期容量(newCapacity)是否在0<x<MAX_ARRAY_SIZE之間,在這區間內就直接將數值返回,不在這區間就去判斷一下是否溢位
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
        ? hugeCapacity(minCapacity)
        : newCapacity;
}

/**
* 判斷大小,是否溢位
*/
private int hugeCapacity(int minCapacity) {
    if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
        throw new OutOfMemoryError();
    }
    return (minCapacity > MAX_ARRAY_SIZE)
        ? minCapacity : MAX_ARRAY_SIZE;
}

 

執行緒安全性

我們先來看看StringBuffer的相關方法:

@Override
public synchronized StringBuffer append(long lng) {
    toStringCache = null;
    super.append(lng);
    return this;
}

/**
 * @throws StringIndexOutOfBoundsException {@inheritDoc}
 * @since      1.2
 */
@Override
public synchronized StringBuffer replace(int start, int end, String str) {
    toStringCache = null;
    super.replace(start, end, str);
    return this;
}

/**
 * @throws StringIndexOutOfBoundsException {@inheritDoc}
 * @since      1.2
 */
@Override
public synchronized String substring(int start) {
    return substring(start, count);
}

@Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}

從上面的原始碼中我們看到幾乎都是所有方法都加了synchronized,幾乎都是呼叫的父類的方法.,用synchronized關鍵字修飾意味著什麼?加鎖,資源同步序列化處理,所以是執行緒安全的。

 

我們再來看看StringBuilder的相關原始碼:

@Override
public StringBuilder append(double d) {
    super.append(d);
    return this;
}

/**
 * @since 1.5
 */
@Override
public StringBuilder appendCodePoint(int codePoint) {
    super.appendCodePoint(codePoint);
    return this;
}

/**
 * @throws StringIndexOutOfBoundsException {@inheritDoc}
 */
@Override
public StringBuilder delete(int start, int end) {
    super.delete(start, end);
    return this;
}

StringBuilder的原始碼裡面,基本上所有方法都沒有用synchronized關鍵字修飾,當多執行緒訪問時,就會出現執行緒安全性問題。

 

為了證明StringBuffer執行緒安全,StringBuilder執行緒不安全,我們通過一段程式碼進行驗證:

測試思想

  • 分別用1000個執行緒寫StringBuffer和StringBuilder,

  • 使用CountDownLatch保證在各自1000個執行緒執行完之後才列印StringBuffer和StringBuilder長度,

  • 觀察結果。

測試程式碼

import java.util.concurrent.CountDownLatch;

public class TestStringBuilderAndStringBuffer {
    public static void main(String[] args) {
        //證明StringBuffer執行緒安全,StringBuilder執行緒不安全
        StringBuffer stringBuffer = new StringBuffer();
        StringBuilder stringBuilder = new StringBuilder();
        CountDownLatch latch1 = new CountDownLatch(1000);
        CountDownLatch latch2 = new CountDownLatch(1000);
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        stringBuilder.append(1);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        latch1.countDown();
                    }
                }
            }).start();
        }
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        stringBuffer.append(1);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        latch2.countDown();
                    }

                }
            }).start();
        }
        try {
            latch1.await();
            System.out.println(stringBuilder.length());
            latch2.await();
            System.out.println(stringBuffer.length());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

測試結果

  • StringBuffer不論執行多少次都是1000長度。

  • StringBuilder絕大多數情況長度都會小於1000。

  • StringBuffer執行緒安全,StringBuilder執行緒不安全得到證明。

總結一下

  • StringBuffer和StringBuilder都繼承自抽象類AbstractStringBuilder。

  • 儲存資料的字元陣列也沒有被final修飾,說明值可以改變,且構造出來的字串還有空餘位置拼接字串,但是拼接下去肯定也有不夠用的時候,這時候它們內部都提供了一個自動擴容機制,當發現長度不夠的時候(預設長度是16),會自動進行擴容工作,擴充套件為原陣列長度的2倍加2,建立一個新的陣列,並將陣列的資料複製到新陣列,所以對於拼接字串效率要比String要高。自動擴容機制是在抽象類中實現的。

  • 執行緒安全性:StringBuffer效率低,執行緒安全,因為StringBuffer中很多方法都被 synchronized 修飾了,多執行緒訪問時,執行緒安全,但是效率低下,因為它有加鎖和釋放鎖的過程。StringBuilder效率高,但是執行緒是不安全的。

 

各位老鐵如果還有別的答案,可以評論留言哈!

 

相關文章