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


面試官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效率高,但是線程是不安全的。

 

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM