StringBuilder_學習筆記

參考:https://www.jianshu.com/p/160c9be0b132java

鏈接符號 "+" 本質

字符串變量(非final修飾)經過 "+" 進行拼接,在編譯過程當中會轉化爲StringBuilder對象的append操做,注意是編譯過程,而不是在JVM中。數組

public class StringTest {
    public static void main(String[] args) {
        String str1 = "hello ";
        String str2 = "java";
        String str3 = str1 + str2 + "!";
        String str4 = new StringBuilder().append(str1).append(str2).append("!").toString();
    }
}

上述 str3 和 str4 的執行效果實際上是同樣的,不過在for循環中,千萬不要使用 "+" 進行字符串拼接。併發

public class test {
    public static void main(String[] args) {
        run1();
        run2();
    }   

    public static void run1() {
        long start = System.currentTimeMillis();
        String result = "";
        for (int i = 0; i < 10000; i++) {
            result += i;
        }
        System.out.println(System.currentTimeMillis() - start);
    }
    
    public static void run2() {
         long start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            builder.append(i);
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}

 

在for循環中使用 "+" 和StringBuilder進行1萬次字符串拼接,耗時狀況以下:
一、使用 "+" 拼接,平均耗時 250ms;
二、使用StringBuilder拼接,平均耗時 1ms;app

for循環中使用 "+" 拼接爲何這麼慢?下面是run1方法的字節碼指令ide

5 ~ 34 行對應for循環的代碼,能夠發現,每次循環都會從新初始化StringBuilder對象,致使性能問題的出現。性能

性能問題

StringBuilder內部維護了一個char[]類型的value,用來保存經過append方法添加的內容,經過 new StringBuilder() 初始化時,char[]的默認長度爲16,若是append第17個字符,會發生什麼?測試

void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}

 

若是value的剩餘容量,沒法添加所有內容,則經過expandCapacity(int minimumCapacity)方法對value進行擴容,其中minimumCapacity = 原value長度 + append添加的內容長度。
一、擴大容量爲原來的兩倍 + 2,爲何要 + 2,而不是恰好兩倍?
二、若是擴容以後,仍是沒法添加所有內容,則將 minimumCapacity 做爲最終的容量大小;
三、利用 System.arraycopy 方法對原value數據進行復制;ui

在使用StringBuilder時,若是給定一個合適的初始值,能夠避免因爲char[]數組屢次複製而致使的性能問題。spa

不一樣初始容量的性能測試:code

public class StringBuilderTest {
    public static void main(String[] args) {
        int sum = 0;
        final int capacity = 40000000;
        for (int i = 0; i < 100; i++) {
            sum += cost(capacity);
        }
        System.out.println(sum / 100);
    }

    public static long cost(int capacity) {
        long start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder(capacity);
        for (int i = 0; i < 10000000; i++) {
            builder.append("java");
        }
        return System.currentTimeMillis() - start;
    }
}

 

執行一千萬次append操做,不一樣初始容量的耗時狀況以下:
一、容量爲默認16時,平均耗時110ms;
二、容量爲40000000時,不會發生複製操做,平均耗時85ms;

經過以上數據能夠發現,性能損耗不是很嚴重。

內存問題

一、StringBuilder內部進行擴容時,會新建一個大小爲原來兩倍+2的char數組,並複製原char數組到新數組,致使內存的消耗,增長GC的壓力。
二、StringBuilder的toString方法,也會形成char數組的浪費。

public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

 

String的構造方法中,會新建一個大小相等的char數組,並使用 System.arraycopy() 複製StringBuilder中char數組的數據,這樣StringBuilder的char數組就白白浪費了。

重用StringBuilder

public class StringBuilderHolder {
    private final StringBuilder sb;
    public StringBuilderHolder(int capacity) {
        sb = new StringBuilder(capacity);
    }

    public StringBuilder resetAndGet() {
        sb.setLength(0);
        return sb;
    }
}

 

經過 sb.setLength(0) 方法能夠把char數組的內存區域設置爲0,這樣char數組重複使用,爲了不併發訪問,能夠在ThreadLocal中使用StringBuilderHolder,使用方式以下:

private static final ThreadLocal<StringBuilderHolder> stringBuilder= new ThreadLocal<StringBuilderHolder>() {
    @Override
    protected StringBuilderHolder initialValue() {
        return new StringBuilderHolder(256);
    }
};
 
StringBuilder sb = stringBuilder.get().resetAndGet();

 

不過這種方式也存在一個問題,該StringBuilder實例的內存空間一直不會被GC回收,若是char數組在某次操做中被擴容到一個很大的值,可能以後很長一段時間都不會用到如此大的空間,就會形成內存的浪費。

總結

雖然使用默認的StringBuilder進行字符串拼接操做,性能消耗不是很嚴重,但在高性能場景下,仍是推薦使用ThreadLocal下可重用的StringBuilder方案。

相關文章
相關標籤/搜索