1. 程式人生 > 實用技巧 >三、String、StringBuffer 和 StringBuilder

三、String、StringBuffer 和 StringBuilder

  String、StringBuffer 和 StringBuilder 都位於 java.lang 包下,都屬於字串操作類。String 使用了final類修飾,是不可變類;StringBuffer 是執行緒安全類,StringBuilder 是非執行緒安全類,巴拉巴拉。。。僅僅知道這些,再往深一點就不太清楚了,本篇博文基於原始碼探討一下這三個類的區別。

  其實,它們三個都實現了Serializable 和 CharSequence 介面;String 同時實現了Comparable 介面用於比較;StringBuffer 和 StringBuilder 繼承自AbstractStringBuilder抽象類。依賴關係如圖所示。

一、String 解析

  String是 final 修飾的不可變類,其內部只有四個非函式成員:

/** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final
long serialVersionUID = -6849794470754667710L; /** * Class String is special cased within the Serialization Stream Protocol. * * A String instance is written into an ObjectOutputStream according to * <a href="{@docRoot}/../platform/serialization/spec/output.html"> * Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

  其中 value 是常量字串陣列,表示這個陣列引用不能再指向其他地址,在String類中都是在建構函式中初始化賦值;hash 用於快取當前字串的雜湊值(稍後會解釋為什麼需要單獨使用一個變數來儲存雜湊值)。

  final 修飾的成員變數要麼定義的時候就初始化,要麼在建構函式中初始化,並且要在全部的建構函式都要初始化;已初始化後不能重新初始化或賦值,否則不能編譯通過。

1. String 的多個建構函式

  

public String() { this.value = "".value;}  // 非必要,因為字串是不可變的
public String(String original) { // 以字串初始化時,只是將這兩個內部成員賦值
    this.value = original.value;
    this.hash = original.hash;
    }
public String(char value[]) { // 其實呼叫的是System.arraycopy底層方法將字元陣列複製一份,陣列引用再指向複製的字元陣列
        this.value = Arrays.copyOf(value, value.length);
    }
public String(byte bytes[]) { // 位元組陣列的建構函式
        this(bytes, 0, bytes.length);
    }
public String(StringBuffer buffer) { // StringBuffer的建構函式,使用的synchronized修飾,考慮到了執行緒安全,實際上是對StringBuffer內的字元陣列複製一份,並使value指向它
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
public String(StringBuilder builder) { // 與StringBuffer同理,只是少了synchronized
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }
// 其他的建構函式更多是字元陣列、整形陣列、位元組陣列位移擷取的操作,原理其實還是將需要的部分生成一份字元陣列,並使value指向它,
// 前面講過,value定義時沒有初始化,全部的建構函式中將value初始化,普通函式不能將value重新指向其他字元陣列,因此String是不可變的字串類,String也無法向外部提供可修改內部value字元陣列的方法

2. String 常用函式解析

  (1)length()

public int length() { // 返回的是字元陣列的長度
        return value.length;
    }

  (2)isEmpty()

public boolean isEmpty() { // 字元陣列長度為0時,即為"".value.length
        return value.length == 0;
    }

  (3)charAt(int index)

public char charAt(int index) { // 越界時會丟擲字串索引越界異常
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

  (4)getBytes()

public byte[] getBytes() { // 獲取字串的位元組陣列,有設定chartset的引數,
        return StringCoding.encode(value, 0, value.length);
    }

  (5)equals(Object anObject)

public boolean equals(Object anObject) { // 重寫了Object的方法
        if (this == anObject) { // 判斷引用是否與自己相等
            return true;
        }
        if (anObject instanceof String) { // 判斷是否是String型別,因此如果是StringBuffer或者StringBuilder,即使字元陣列內容相同也返回的是false,如果相比較可以使用contentEquals
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) { // 判斷長度是否相同
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

  (6)compareTo(String anotherString)

public int compareTo(String anotherString) { // 實現的是Comparable的方法,用於字串的比較,原理是字元陣列元素依次比較
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

  (7)hashCode()

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) { // 0代表hash沒有被計算出來,已有快取的hash值直接返回即可,快取hash省去了每次的計算
            char val[] = value;
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

  這是重頭戲,String重寫了Object的 hashCode() 方法,從字元陣列的首位開始,當前hash值*32 + 當前字元轉變的int型數值,雜湊計算公式可以計為s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]。

二、AbstractStringBuilder 解析

  在分析 StringBuffer 和 StringBuilder 之前,先分析一下它們的共同繼承類 AbstractStringBuilder。

  在程式碼中可以看到,它的內部有兩個成員變數:

/**
     * The value is used for character storage.
     */
    char[] value; // 字元陣列    注意!!這裡的value沒有使用final修飾,可以重新賦值

    /**
     * The count is the number of characters used.
     */
    int count; // value中真正已使用的長度,count<=value.length

  定義了兩個建構函式,一個無引數;一個有引數,初始化了value的長度:

/**
     * 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];
    }

  同時,重寫了CharSequence的 length() 方法,返回的不是 value 陣列的實際長度,而是其已使用的長度,即 count 值。

@Override
 public int length() {
       return count;
 }

  AbstractStringBuilder提供的諸如 charAt、replace、substring 等String類中同樣有的方法越界問題也是使用的count限制而不是value的實際長度;同時它提供了String類沒有的方法如:append、insert、delete以及reverse 等函式。

  append 方法返回的是自身AbstractStringBuilder物件的引用,以 append(String str) 為例

  public AbstractStringBuilder append(String str) {
        if (str == null) // 為空時新增"null"
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len); // 確保value容量 當前已佔用 + str.length()
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

    private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4); // 確保value容量 當前已佔用 + "null".length()
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c; //已佔用數量增加 4
        return this;
    }

    private void ensureCapacityInternal(int minimumCapacity) { // 確認容量是否需要擴容
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) { // 如果當前容量不足,通過需要容量計算出容量,複製資料至新陣列
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity)); // 注意!!這裡的value被重新賦值了
        }
    }

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2; // 擴容為 (原來的容量+1)* 2
        if (newCapacity - minCapacity < 0) { // 擴容2倍仍不滿足,直接去需要的容量
            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;
  }

  AbstractStringBuilder 內部只有一個 abstract 函式,就是 toString() 函式,這個函式交給它的子類重寫。

@Override
public abstract String toString();

  AbstractStringBuilder 的子類使用了設計模式 建造者模式(將一個複雜物件的構建和它的表示相分離)。

三、StringBuilder 解析

  StringBuilder 提供了4個建構函式,構造方法中的第一步是 呼叫初始化字元陣列父類建構函式初始化字元陣列的容量,預設是 16,當賦值String 或 CharSequence時,初始化為為它的長度 + 16,然後呼叫append方法,它的 append 方法直接使用的是 AbstractStringBuilder 的實現方式。

  /**
     * Constructs a string builder with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuilder() {
        super(16);
    }

    /**
     * Constructs a string builder with no characters in it and an
     * initial capacity specified by the {@code capacity} argument.
     *
     * @param      capacity  the initial capacity.
     * @throws     NegativeArraySizeException  if the {@code capacity}
     *               argument is less than {@code 0}.
     */
    public StringBuilder(int capacity) {
        super(capacity);
    }

    /**
     * Constructs a string builder initialized to the contents of the
     * specified string. The initial capacity of the string builder is
     * {@code 16} plus the length of the string argument.
     *
     * @param   str   the initial contents of the buffer.
     */
    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }

    /**
     * Constructs a string builder that contains the same characters
     * as the specified {@code CharSequence}. The initial capacity of
     * the string builder is {@code 16} plus the length of the
     * {@code CharSequence} argument.
     *
     * @param      seq   the sequence to copy.
     */
    public StringBuilder(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

  StringBuilder 的字串操作方法 append、delete、insert、replace、indexOf、reverse 等方法都在 AbstractStringBuilder 中已實現,直接使用 super 呼叫,不同的是返回型別是StringBuilder型別的物件引用,不是 AbstractStringBuilder 型別的了。

  前面講到,AbstractStringBuilder 中的 toString() 函式定義為抽象函式,沒有了方法體,需要由子類實現,StringBuilder 的 toString() 實現如下:

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

  即新生成了一個String物件。

  StringBuilder有兩個序列化與反序列化的方法 writeObject 和 readObject:

  /**
     * Save the state of the {@code StringBuilder} instance to a stream
     * (that is, serialize it).
     *
     * @serialData the number of characters currently stored in the string
     *             builder ({@code int}), followed by the characters in the
     *             string builder ({@code char[]}).   The length of the
     *             {@code char} array may be greater than the number of
     *             characters currently stored in the string builder, in which
     *             case extra characters are ignored.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        s.defaultWriteObject();
        s.writeInt(count); // 儲存的value陣列實際使用的數量
        s.writeObject(value); // 字元陣列
    }

    /**
     * readObject is called to restore the state of the StringBuffer from
     * a stream.
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        count = s.readInt();
        value = (char[]) s.readObject();
    }

四、StringBuffer 解析

  StringBuffer 出現的實際上比 StringBuilder 早,StringBuffer 在 JDK1.0 就已經推出,StringBuilder 到了JDK1.5 才推出。然而 StringBuffer 在JDK1.5時程式碼經過了完善,可以多執行緒使用,在單執行緒的情況想,StringBuilder更有優勢。

  StringBuffer 在 AbstractStringBuffer 原有成員變數 value(char[]) 和 count (int) 增加了成員變數toStringCache(char[])

  /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;

  toStringCache 用於呼叫 toString()方法後字元數字中已使用長度(及count以內)的字元陣列的賦值,下次呼叫時不需要重新獲取。該變數使用 transient 修飾,表示該變數在序列化的時候不需要序列化該變數。在 StringBuffer 中的字串變更操作函式如 append、insert 、reverse 中,第一步就是 “toStringCache = null”。

  toString() 函式實現如下:

    @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true); // 返回的是通過容量count的得到的 toStringCache 生成的新字串
    }

  StringBuffer 中的構造方法與 StringBuilder 類似,同樣是4個建構函式:

    public StringBuffer() {
        super(16);
    }
    public StringBuffer(int capacity) {
        super(capacity);
    }
    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }
    public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

  在其他方法中,除了 readObject 函式,所有的函式都直接或者間接的通過 synchronized 對函式進行了修飾,從而實現了執行緒同步,函式在實現內容上跟 StringBuilder 上沒有什麼差別,都是使用的 AbstractStringBuffer 已實現好的內容。

  StringBuffer的序列化與反序列化相關程式碼實現如下:

    private static final java.io.ObjectStreamField[] serialPersistentFields =
    {
        // 通過基本型別獲取相應包裝類的Class
        // public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int")
        //  public static final Class<Boolean> TYPE = (Class<Boolean>) Class.getPrimitiveClass("boolean");
        new java.io.ObjectStreamField("value", char[].class),
        new java.io.ObjectStreamField("count", Integer.TYPE),
        new java.io.ObjectStreamField("shared", Boolean.TYPE),
    };

    // 方法通過 synchronized 修飾實現執行緒同步
    private synchronized void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        java.io.ObjectOutputStream.PutField fields = s.putFields();
        fields.put("value", value);
        fields.put("count", count);
        fields.put("shared", false);
        s.writeFields();
    }

    // 沒有通過 synchronized 修飾
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        java.io.ObjectInputStream.GetField fields = s.readFields();
        value = (char[])fields.get("value", null);
        count = fields.get("count", 0);
    }            

toStringCache