ソース分析から:JavaのStringBuilderを解析する

17562 ワード

Javaでは,1つの文字列,例えば削除操作を+で直接行うと効率が低いことが知られている.代替として、このような動作は、一般にStringBuilderを用いて行われる.
そこで今回は、StringBuilderのソースコードを検討してみましょう
コンストラクタ
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

    /** use serialVersionUID for interoperability */
    static final long serialVersionUID = 4383685877147921099L;

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

    ...

}

初期容量を指定しない場合、親AbstractStringBuilderのコンストラクション関数が呼び出され、デフォルトの初期容量は16であることがわかります.
そこで、「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(int capacity) {
        value = new char[capacity];
    }

ここでは、設定された初期容量に基づいて、対応する長さのchar型配列valueが初期化されていることがわかる.
appendメソッド
ここではstrのappendメソッドを例に、StringBuilderのappendメソッドがどのように動作しているかを見てみましょう.
まずStringBuilderのうちappend(String str)の方法です.
public StringBuilder append(String str) {
    super.append(str);
    return this;
}

親のappendメソッドを指します.次に、親の定義を見てみましょう.
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

このうちensureCapacityInternalは、この入力を容量で装着できるstrを保証するためである.
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

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

ここでの拡張方式は、まず容量を2に乗じて2を加え、必要な容量と比較し、必要な容量より小さい場合は必要な容量を取り、後にオーバーフロー防止の操作があり、非常に厳格に書かれており、ここでは学習と参考に値する.更新された容量を取得した後、新しい容量の配列を作成し、前のデータをArrays.copyOf()メソッドでコピーし、新しいメンバー変数valueに更新します.appendメソッドに戻り、ensureCapacityInternalが完了すると、必要な追加データが追加されます.ここでは、StringgetCharsメソッドが呼び出されます.
# String.java
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    if (srcBegin < 0) {
        throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > value.length) {
        throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd) {
        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

ここでは、System.arraycopyを呼び出して、String内部のメンバー変数char型配列value(ここではString、すなわちappendのパラメータ内の変数)を0からstr.length()、すなわちすべてのメンバー、すなわちdst、すなわちStringBuilderのメンバー変数valueにコピーする.その後、append全体の動作が完了した.