對比String、StringBuilder字符串的連接效率及StringBuilder和StringBuffer線程安全的比較

一、字符串連接的效率問題

使用String連接字符串時爲什麼慢?

小知識點

java中對數組進行初始化後,該數組所佔的內存空間、數組長度都是不可變的。

創建一個字符串,爲字符串對象分配內存空間,會耗費掉一定的時間(CPU)與空間(內存)代價,作爲最基礎的數據類型,大量頻繁的創建字符串,極大程度地影響程序的性能。

過多無用的中間對象

每次連接字符串時都會創建一個新的String對象,隨着拼接次數的增多,這個對象會越來越大。
如,進行100次拼接需要創建100個String對象才能夠達到目的。

StringBuilder在連接時爲什麼效率更高?

字符數組的擴容機制:

private void ensureCapacityInternal(int minimumCapacity) {
         // 最小所需容量minimumCapacity是否比原數組長度要長
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
    
private int newCapacity(int minCapacity) {
         // 計算擴容之後的容量newCapacity
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        // 擴容後還小於所需的最小容量
        if (newCapacity - minCapacity < 0) {
            // 設置新容量爲最小所需容量minimumCapacity
            newCapacity = minCapacity;
        }
        // newCapacity是否溢出,newCapacity是否比數組所能分配的最大容量 MAX_ARRAY_SIZE 還要大。
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

    private int hugeCapacity(int minCapacity) {
        // 最小所需容量minCapacity大於Integer.MAX_VALUE時拋出內存溢出異常
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        // 如果minCapacity介於MAX_ARRAY_SIZE和Integer.MAX_VALUE之間,則新的容量爲minCapacity,否則直接使用MAX_ARRAY_SIZE作爲新的容量。
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }

向原StringBuilder對象中追加字符串時:

1.追加對象str爲null時追加'null'字符

2.確認是否需要進行擴容操作

2.1 最小所需容量minimumCapacity是否比原數組長度要長,即當原數組長度不能滿足所需最小容量時進行擴容操作。  
2.2 計算擴容之後的容量newCapacity,newCapacity = (value.length * 2) + 2。  
2.3 擴容後是否還小於所需的最小容量,如果小於則直接設置新容量爲最小所需容量minimumCapacity。  
2.4 newCapacity是否溢出,newCapacity是否比數組所能分配的最大容量 MAX_ARRAY_SIZE 還要大。如果是的話則判斷,最小所需容量minCapacity大於Integer.MAX_VALUE時拋出內存溢出異常,如果minCapacity介於MAX_ARRAY_SIZE和Integer.MAX_VALUE之間,則新的容量爲minCapacity,否則直接使用MAX_ARRAY_SIZE作爲新的容量。  

3.str.getChars()將str追加到value的末尾

效率高的原因

  1. 擴容機制保證了,只有在滿足擴容條件 minimumCapacity - value.length > 0 時纔會進行擴容生成新的數組,所以大部分情況都是在對原數組進行操作,避免了產生過多的無用char[]對象,節省了系統資源的開銷。

代碼

/**
 * 比較字符串連接速度
 *
 * @Author: lingyejun
 * @Date: 2019/8/17
 * @Describe:
 * @Modified By:
 */
public class LinkCompare {

    /**
     * 原始字符串連接
     *
     * @param times
     */
    public static void linkByString(int times) {

        Long startTime = System.currentTimeMillis();

        String initStr = "";
        for (int i = 0; i < times; i++) {
            initStr = initStr + i;
        }

        Long endTime = System.currentTimeMillis();

        System.out.println("String 連接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");
    }

    /**
     * 使用StringBuilder連接字符串
     *
     * @param times
     */
    public static void linkByStringBuilder(int times) {

        Long startTime = System.currentTimeMillis();

        StringBuilder initStr = new StringBuilder();
        for (int i = 0; i < times; i++) {
            initStr.append(i);
        }

        Long endTime = System.currentTimeMillis();

        System.out.println("StringBuilder 連接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");
    }


    /**
     * 使用StringBuffer連接字符串
     *
     * @param times
     */
    public static void linkByStringBuffer(int times) {

        Long startTime = System.currentTimeMillis();

        StringBuffer initStr = new StringBuffer();
        for (int i = 0; i < times; i++) {
            initStr.append(i);
        }

        Long endTime = System.currentTimeMillis();

        System.out.println("StringBuffer 連接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");
    }


    public static void main(String[] args) {

        // 100000000
        linkByStringBuilder(40000);
        //-XX:+PrintGCDetails
        //linkByString(40000);

    }
}

二、StringBuilder和String Buffer的線程安全比較

驗證StringBuffer的線程安全性

線程不安全的原因

public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    
public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

測試代碼

import java.util.ArrayList;
import java.util.List;

/**
 * StringBuilder和StringBuffer的併發測驗
 *
 * @Author: lingyejun
 * @Date: 2019/8/17
 * @Describe:
 * @Modified By:
 */
public class SecurityCompare {

    public void stringBuilderTest() {

        // 初始化StringBuilder
        StringBuilder stringBuilder = new StringBuilder();

        // joinList
        List<StringBuilderThread> joinList = new ArrayList<>();

        // 模擬併發場景
        for (int i = 0; i < 1000; i++) {
            StringBuilderThread sbt = new StringBuilderThread(stringBuilder);
            sbt.start();
            joinList.add(sbt);
        }

        // 等待append線程執行完畢後再執行主線程
        for (StringBuilderThread thread : joinList) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 打印最終的結果
        System.out.println("StringBuilder 併發append的結果: " + stringBuilder.length());
    }

    public void stringBufferTest() {

        // 初始化StringBuffer
        StringBuffer stringBuffer = new StringBuffer();

        // joinList
        List<StringBufferThread> joinList = new ArrayList<>();

        // 模擬併發場景
        for (int i = 0; i < 1000; i++) {
            StringBufferThread sbf = new StringBufferThread(stringBuffer);
            sbf.start();
            joinList.add(sbf);
        }

        // 等待append線程執行完畢後再執行主線程
        for (StringBufferThread thread : joinList) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 打印最終的結果
        System.out.println("StringBuffer 併發append的結果: " + stringBuffer.length());
    }


    public static void main(String[] args) {

        SecurityCompare securityCompare = new SecurityCompare();

        securityCompare.stringBuilderTest();
        securityCompare.stringBufferTest();

    }

    public static class StringBuilderThread extends Thread {

        private StringBuilder stringBuilder;

        public StringBuilderThread(StringBuilder stringBuilder) {
            this.stringBuilder = stringBuilder;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stringBuilder.append("a");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    private static class StringBufferThread extends Thread {

        private StringBuffer stringBuffer;

        public StringBufferThread(StringBuffer stringBuffer) {
            this.stringBuffer = stringBuffer;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stringBuffer.append("a");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

三、結論

1.String爲固定長度的字符串,StringBuilder和StringBuffer爲變長字符串。
2.StringBuffer是線程安全的,StringBuilder是非線程安全的。
3.StringBuilder和StringBuffer的默認初始容量是16,可以提前預估好字符串的長度,進一步減少擴容帶來的額外開銷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章