1. 程式人生 > >STRING-STRINGBUFFER-STRINGBUILDER的區別和源碼分析

STRING-STRINGBUFFER-STRINGBUILDER的區別和源碼分析

解釋 構造函數 一個 ria The ade zed ans 程序

一,String,StringBuffer,StringBuilder三者之間的關系
  三個類的關系:StringBuffer和StringBuilder都繼承自AbstractStringBuilder這個類,

  而AbstractStringBuilder和String都繼承自Object這個類(Object是所有java類的超類)

  可以通過如下的部分源碼看到:

  String:

    public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    。。。。。
}

StringBuffer:

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

StringBuilder:

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

二,String是不可變類,而StringBuffer, StringBuilder是可變類

  我們查看這三個類的源碼,發現String類沒有append()、delete()、insert()這三個成員方法,而StringBuffer和StringBuilder都有這些方法,StringBuffer和StringBuilder中的這三個方法都是通過system類的arraycopy方法來實現的,即將原數組復制到目標數組

1.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);
        }
       //當對原來的字符串進行截取的時候(beginIndex >0),返回的結果是新建的對象
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

當我們對字符串從第beginIndex(beginIndex >0) 個字符開始進行截取時,返回的結果是重新new出來的對象。所以,在對String類型的字符串進行大量“插入”和“刪除”操作時會產生大量的臨時變量。

2.StringBuffer與StringBuilder:

因為StringBuffer和StringBuilder都繼承自這個抽象類,即AbstractStringBuilder類是StringBuffer和StringBuilder的共同父類。AbstractStringBuilder類的關鍵代碼片段如下:

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;//一個char類型的數組,非final類型,這一點與String類不同

    /**
     * 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];//構建了長度為capacity大小的數組
    }

//其他代碼省略……
……
}

StringBuffer:

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
   /**
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuffer() {
        super(16);//創建一個默認大小為16的char型數組
    }

    /**
     * Constructs a string buffer with no characters in it and
     * the specified initial capacity.
     *
     * @param      capacity  the initial capacity.
     * @exception  NegativeArraySizeException  if the {@code capacity}
     *               argument is less than {@code 0}.
     */
    public StringBuffer(int capacity) {
        super(capacity);//自定義創建大小為capacity的char型數組
    }
//省略其他代碼……

StringBuilder類的構造函數與StringBuffer類的構造函數實現方式相同,此處就不貼代碼了。
下面來看看StringBuilder類的append方法和insert方法的代碼,因StringBuilder和StringBuffer的方法實現基本上一致,不同的是StringBuffer類的方法前多了個synchronized關鍵字,
即StringBuffer是線程安全的。所以接下來我們就只分析StringBuilder類的代碼了。StringBuilder類的append方法,insert方法都是Override 父類AbstractStringBuilder的方法,
所以我們直接來分析AbstractStringBuilder類的相關方法。

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
       //調用下面的ensureCapacityInternal方法
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
           //調用下面的expandCapacity方法實現“擴容”特性
            expandCapacity(minimumCapacity);
    }

   /**
     * This implements the expansion semantics of ensureCapacity with no
     * size check or synchronization.
     */
    void expandCapacity(int minimumCapacity) {
       //“擴展”的數組長度是按“擴展”前數組長度的2倍再加上2 byte的規則來擴展
        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返回的新的char[]對象,從而達到“擴容”的特性
        value = Arrays.copyOf(value, newCapacity);
    }

從上述代碼分析得出,StringBuilder和StringBuffer的append方法“擴容”特性本質上是通過調用Arrays類的copyOf方法來實現的。接下來我們順藤摸瓜,再分析下Arrays.copyOf(value, newCapacity)這個方法吧。代碼如下:

public static char[] copyOf(char[] original, int newLength) {
        //創建長度為newLength的char數組,也就是“擴容”後的char 數組,並作為返回值
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;//返回“擴容”後的數組變量
    }

其中,insert方法也是調用了expandCapacity方法來實現“擴容”特性的,此處就不在贅述了。

接下來,分析下delete(int start, int end)方法,代碼如下:

public AbstractStringBuilder delete(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            end = count;
        if (start > end)
            throw new StringIndexOutOfBoundsException();
        int len = end - start;
        if (len > 0) {
            //調用native方法arraycopy對value數組進行復制操作,然後重新賦值count變量達到“刪除”特性
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return this;
    }

從源碼可以看出delete方法的“刪除”特性是調用native方法arraycopy對value數組進行復制操作,然後重新賦值count變量實現的

最後,來看下substring方法,源碼如下 :

public String substring(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            throw new StringIndexOutOfBoundsException(end);
        if (start > end)
            throw new StringIndexOutOfBoundsException(end - start);
        //根據start,end參數創建String對象並返回
        return new String(value, start, end - start);
    }

三,運行速度:
總體上:String小於StringBuffer小於StringBuilder

原因:String是不可變的,為字符串常量,每次對 String 類型進行改變的時候其實都等同於生成了一個新的 String 對象,然後將指針指向新的 String 對象。這就會對程序運行產生很大的影響,因為當內存中的無引用對象多了以後,JVM的GC進程就會進行垃圾回收,這個過程會耗費很長一段時間,因此經常改變內容的字符串最好不要用 String類的對象。而如果是使用 StringBuffer 類則結果就不一樣了,每次結果都會對 StringBuffer 對象本身進行操作,而不是生成新的對象,再改變對象引用。所以在一般情況下我們推薦使用 StringBuffer ,特別是字符串對象經常改變的情況下。
註意:但是在某些特殊情況下,String對象的字符串拼接其實是被JVM解釋成了StringBuffer對象的拼接,所以這時候String對象的速度並不比StringBuffer對象慢。如:

String  m = "I"+"am"+"boy";
StringBuffer n = new StringBuffer("I").append("am").append("boy");

但是如果要拼接的字符串來自於不同的String對象的話,那結果就不一樣了(常用),StringBuffer要比String快的多,如:

 String p = "China is";
            String q = "very good";
            String t = p + q;

在運行速度方面StringBuffer<StringBuilder,這是因為StringBuffer由於線程安全的特性,常常應用於多線程的程序中,為了保證多線程同步一些線程就會遇到阻塞的情況,這就使得StringBuffer的運行時間增加,從而使得運行速度減慢;而StringBuilder通常不會出現多線程的情況,所以運行時就不會被阻塞,運行速度也自然就比StringBuffer快了。

StringBuffer和StringBuilder性能測試程序:

 public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        long builderThreadStartTime = (new Date()).getTime();//builder 多線程開始時間
        for(int i=0;i<10000000;i++){
            sb.append(i);
        }
        long builderThreadEndTime = (new Date()).getTime();//builder 多線程結束時間
        System.out.println("builder 多線程消耗時間==="+(builderThreadEndTime-builderThreadStartTime));
        StringBuffer sbf = new StringBuffer();
        long bufferThreadStartTime = (new Date()).getTime();//buffer 單線程開始時間
        for(int i=0;i<10000000;i++){
            sbf.append(i);
        }
        long bufferThreadEndTime = (new Date()).getTime();//buffer 單線程結束時間
        System.out.println("buffer 單線程消耗時間==="+(bufferThreadEndTime-bufferThreadStartTime));
    }

輸出結果:

builder 多線程消耗時間===391
buffer  單線程消耗時間===611

四,線程安全與不安全

  StringBuffer是線程安全的,StringBuilder是非線程安全的

  部分源碼:StringBuffer:

    public synchronized String substring(int start) {
        return substring(start, count);
    }

    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @since      1.4
     */
    @Override
    public synchronized CharSequence subSequence(int start, int end) {
        return super.substring(start, end);
    }

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

    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     * @since      1.2
     */
    @Override
    public synchronized StringBuffer insert(int index, char[] str, int offset,
                                            int len)
    {
        toStringCache = null;
        super.insert(index, str, offset, len);
        return this;
    }

    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public synchronized StringBuffer insert(int offset, Object obj) {
        toStringCache = null;
        super.insert(offset, String.valueOf(obj));
        return this;
    }

StringBuilder:

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

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

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

我們可以發現StringBuffer類中的大部分成員方法都被synchronized關鍵字修飾,而StringBuilder類沒有出現synchronized關鍵字;至於StringBuffer類中那些沒有用synchronized修飾的成員方法,如insert()、indexOf()等,通過源碼上的註釋可以知道,它們是調用StringBuffer類的其他方法來實現同步的。註意:toString()方法也是被synchronized關鍵字修飾的

五,synchronized關鍵字解析:

一、修飾一個代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的範圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;

當兩個並發線程訪問同一個對象中的synchronized(this){}同步代碼塊時,同一時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊;
當一個線程訪問對象中的一個synchronized(this){}同步代碼塊時,另一個線程仍然可以訪問該對象中的非synchronized(this){}同步代碼塊;
當一個線程訪問對象中的一個synchronized(this){}同步代碼塊時,其他線程對對象中所有其它synchronized(this){}同步代碼塊的訪問將被阻塞。
二、修飾一個方法,被修飾的方法稱為同步方法,其作用的範圍是整個方法,作用的對象是調用這個方法的對象;

三、修飾一個靜態的方法,其作用的範圍是整個靜態方法,作用的對象是這個類的所有對象;

因為靜態方法(或變量)是屬於其所屬類的,而不是屬於該類的對象的,所以synchronized關鍵字修飾的靜態方法鎖定的是這個類的所有對象,即所有對象都是同一把鎖。
六, String, StringBuffer, StringBuilder都能夠用final關鍵字修飾,此處不再過多解釋。

但需要註意的是:

final修飾的類不能被繼承;
final修飾的方法不能被繼承類重寫;
final修飾的變量為常量,不能被改變。
七,總結:
1、String類型的字符串對象是不可變的,一旦String對象創建後,包含在這個對象中的字符系列是不可以改變的,直到這個對象被銷毀。
2、StringBuilder和StringBuffer類型的字符串是可變的,不同的是StringBuffer類型的是線程安全的,而StringBuilder不是線程安全的
3、如果是多線程環境下涉及到共享變量的插入和刪除操作,StringBuffer則是首選。如果是非多線程操作並且有大量的字符串拼接,插入,刪除操作則StringBuilder是首選。畢竟String類是通過創建臨時變量來實現字符串拼接的,耗內存還效率不高,怎麽說StringBuilder是通過JNI方式實現終極操作的。
4、StringBuilder和StringBuffer的“可變”特性總結如下:
(1)append,insert,delete方法最根本上都是調用System.arraycopy()這個方法來達到目的
(2)substring(int, int)方法是通過重新new String(value, start, end - start)的方式來達到目的。因此,在執行substring操作時,StringBuilder和String基本上沒什麽區別。

5、為什麽String是Final類型,確可以進行+等操作呢?

答:因為String的+操作實際是通過StringBuffer的append方法進行操作,然後又通過StringBuffer的toString()操作重新賦值的。

STRING-STRINGBUFFER-STRINGBUILDER的區別和源碼分析