java中String StringBuilder StringBuffer比較和效率(性能)測試


 

string stringbuilder stringbuffer三者的區別

從JDK源碼看,String、StringBuilder、StringBuffer都是存放在char[] 數組字符串。
簡單看下三者的部分源碼:
String定義屬性和構造方法:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
   
    private final char value[];
 public String() {
        this.value = "".value;
    }    
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

StringBuilder源碼:

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

   
    public StringBuilder() {
        super(16);
    }

    public StringBuilder(int capacity) {
        super(capacity);
    }


    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }

StringBuffer源碼:

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

    private transient char[] toStringCache;
 
    public StringBuffer() {
        super(16);
    }

比較明顯的是:
String 中定義的char[] 數組是用final 修飾,所以,String 是不可變字符序列,而StringBuilder和StringBuffer是可變字符序列;
如果Sting 需要改變則需要重新創建新對象;
StringBuffer 和 StringBuilder 都繼承 AbstractStringBuilder類,他們在初始化時,都是調用父類的構造器。

接下來,我們在簡單看下AbstractStringBuilder類源碼:

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /** * The value is used for character storage. */
    char[] value;

    /** * The count is the number of characters used. */
    int count;

    /** * This no-arg constructor is necessary for serialization of subclasses. */
    AbstractStringBuilder() {
    }

    /** * Creates an AbstractStringBuilder of the specified capacity. */
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

可以看到 AbstractStringBuilder 其實也定義了char[] 數組,不同的是,AbstractStringBuilder 中的char[] 數組可以可變的,在細看一點,可以看到AbstractStringBuilder 有擴容的方法:

private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        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;
    }

接下來我們繼續,看下String 、StringBuffer 和 StringBuilder的常用方法:
String的常用方法:

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

   
    public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

StringBuilder的常用方法:

 @Override
    public StringBuilder append(int i) {
        super.append(i);
        return this;
    }

    @Override
    public StringBuilder append(long lng) {
        super.append(lng);
        return this;
    }

    @Override
    public StringBuilder append(float f) {
        super.append(f);
        return this;
    }

StringBuffer的常用方法:

 @Override
    public synchronized StringBuffer append(CharSequence s, int start, int end)
    {
        toStringCache = null;
        super.append(s, start, end);
        return this;
    }

    @Override
    public synchronized StringBuffer append(char[] str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

從它們的常用方法可以看出:
String 每次返回的都是新字符串,所以我們使用String的方法操作字符串后不影響原來的字符串;
StringBuffer 和 StringBuilder 返回的都是this,也就是對象本身,所有我們可以在代碼中連着寫append(xx).append(xxx).append(xxx);
不同的是StringBuffer的方法就加了synchronized 也就是我們說的線程安全。
總結一下:

 

 

 

 

String StringBuilder StringBuffer效率(性能)測試

我們通過各自拼接10000字符串來比較一下三者在執行時對時間和對內存資源的占用。
下面是測試代碼:

package com.xzlf.string;

public class TestString {
	public static void main(String[] args) {
		// 使用 String 進行字符拼接
		String str = "";
		long num1 = Runtime.getRuntime().freeMemory();// 獲取系統剩余內存空間
		long time1 = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			str += i; // 相當於產生了5000個對象
		}
		long num2 = Runtime.getRuntime().freeMemory();
		long time2 = System.currentTimeMillis();
		System.out.println("String 占用了內存:" + (num1 - num2));
		System.out.println("String 占用了時間:" + (time2 - time1));
		
		// 使用 StringBuilder 進行字符串拼接
		StringBuilder sb = new StringBuilder("");
		long num3 = Runtime.getRuntime().freeMemory();
		long time3 = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			sb.append(i);
		}
		long num4 = Runtime.getRuntime().freeMemory();
		long time4 = System.currentTimeMillis();
		System.out.println("StringBuilder 占用了內存:" + (num3 - num4));
		System.out.println("StringBuilder 占用了時間:" + (time4 - time3));
		
		// 使用 StringBuilder 進行字符串拼接
		StringBuffer sb2 = new StringBuffer("");
		long num5 = Runtime.getRuntime().freeMemory();
		long time5 = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			sb2.append(i);
		}
		long num6 = Runtime.getRuntime().freeMemory();
		long time6 = System.currentTimeMillis();
		System.out.println("StringBuffer 占用了內存:" + (num5 - num6));
		System.out.println("StringBuffer 占用了時間:" + (time6 - time5));
		
	}
}

以上代碼運行結果為:
在這里插入圖片描述
可以看到,String創建了大量無用對象,消耗了大量內存耗時上大概是StringBuffer 和 builder的100倍。

當然,我們只循環了10000次,StringBuilder的優勢不是很明顯,為了防止java 虛擬機 jvm 垃圾回收機制的干擾 我們我StringBuilder 和 StringBuffer 單獨拿出來吧循環次數加到10萬次、100萬次和1000萬次測試:
代碼吧String部分注釋掉,由於循環次數較多,jvm 在運行時會有垃圾回收,內存對比會不正確,也先注釋:

package com.xzlf.string;

public class TestString {
	public static void main(String[] args) {
		// 使用 String 進行字符拼接
// String str = "";
// long num1 = Runtime.getRuntime().freeMemory();// 獲取系統剩余內存空間
// long time1 = System.currentTimeMillis();
// for (int i = 0; i < 10000; i++) {
// str += i; // 相當於產生了5000個對象
// }
// long num2 = Runtime.getRuntime().freeMemory();
// long time2 = System.currentTimeMillis();
// System.out.println("String 占用了內存:" + (num1 - num2));
// System.out.println("String 占用了時間:" + (time2 - time1));
		
		// 使用 StringBuilder 進行字符串拼接
		StringBuilder sb = new StringBuilder("");
		long num3 = Runtime.getRuntime().freeMemory();
		long time3 = System.currentTimeMillis();
		for (int i = 0; i < 10000000; i++) {
			sb.append(i);
		}
		long num4 = Runtime.getRuntime().freeMemory();
		long time4 = System.currentTimeMillis();
// System.out.println("StringBuilder 占用了內存:" + (num3 - num4));
		System.out.println("StringBuilder 占用了時間:" + (time4 - time3));
		
		// 使用 StringBuilder 進行字符串拼接
		StringBuffer sb2 = new StringBuffer("");
		long num5 = Runtime.getRuntime().freeMemory();
		long time5 = System.currentTimeMillis();
		for (int i = 0; i < 10000000; i++) {
			sb2.append(i);
		}
		long num6 = Runtime.getRuntime().freeMemory();
		long time6 = System.currentTimeMillis();
// System.out.println("StringBuffer 占用了內存:" + (num5 - num6));
		System.out.println("StringBuffer 占用了時間:" + (time6 - time5));
		
	}
}

我這邊測試10萬次結果為:
在這里插入圖片描述
100萬次結果為:
在這里插入圖片描述
1000萬次結果為:
在這里插入圖片描述
在數量太少的情況下,StringBuilder 在StringBuffer加鎖的情況下,並沒有體現出優勢,反而StringBuffer 更勝一籌。
這種情況相信很多測試過的小伙伴也應該遇到過???

對於這種情況,其實也不難理解,append的操作本質還是操作char[] 數組,我們還是繼續看源碼,
StringBuffer比StringBuilder多了一個緩沖區,
我們看下StringBuffer的toString方法:

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

StringBuilder 的toString()方法:

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

我們可以看到StringBuffer的緩存有數據時,就直接在緩存區取,而StringBuilder每次都是直接copy。這樣StringBuffer 相對StringBuilder來說其實是做了一個性能上的優化,所有只有當數量足夠大,StringBuffer的緩沖區填補不了加鎖影響的性能時,StringBuilder才在性能上展現出了它的優勢


免責聲明!

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



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