StringBuilder と StringBuffer は、文字列を操作するためによく使用される 2 つのクラスです。ご存知のとおり、StringBuilder はスレッド セーフではありませんが、StringBuffer はスレッド セーフです。前者は JDK1.5 で追加され、後者は JDK1.0 で利用可能になりました。内部実装を分析してみましょう。
1. 継承関係
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence
2 つのクラスの継承関係は全く同じであることがわかります。 Serializable はシリアル化できるフラグです。 CharSequence インターフェイスには、charAt()、length()、subSequence()、および toString() メソッドが含まれています。String クラスもこのインターフェイスを実装します。ここで注目するのは、StringBuilder と StringBuffer のほとんどの操作の実装をカプセル化する抽象クラス AbstractStringBuilder です。
2. AbstractStringBuilder
1. 変数と構築メソッド
char[] value; int count; AbstractStringBuilder() { } AbstractStringBuilder(int capacity) { value = new char[capacity]; }
AbstractStringBuilder は内部で char[] 配列を使用して文字列を保存し、構築時に初期容量メソッドを指定できます。
2. 容量の拡張
public void ensureCapacity(int minimumCapacity) { if (minimumCapacity > 0) ensureCapacityInternal(minimumCapacity); } private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) expandCapacity(minimumCapacity); } void expandCapacity(int minimumCapacity) { 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.copyOf(value, newCapacity); }
拡張メソッドは、最初にexpandCapacity()によって実装されます。この時点でまだ指定された容量に達していない場合は、容量を元の容量に2を加えた値に拡張します。の場合、新しい容量は次のようになります。容量は minimumCapacity に設定されます。次に、オーバーフローするかどうかを判断します。オーバーフローする場合は、容量を Integer.MAX_VALUE に設定します。最後に値がコピーされますが、これは明らかに時間のかかる操作です。
3. 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; }
append() は最も一般的に使用されるメソッドであり、さまざまな形式のオーバーロードがあります。上記はそのうちの 1 つで、文字列を追加するために使用されます。 str が null の場合、appendNull() メソッドが呼び出されます。このメソッドは実際に文字「n」、「u」、「l」、および「l」を追加します。 null でない場合は、最初に容量が拡張され、次に String の getChars() メソッドが呼び出され、値の末尾に str が追加されます。最後に、オブジェクト自体が返されるため、append() を継続的に呼び出すことができます。
3. StringBuilder
AbstractStringBuilder には、必要なメソッドのほとんどが StringBuilder と StringBuffer を呼び出すだけで実装されています。 StringBuilder の実装を見てみましょう。
1. コンストラクター
public StringBuilder() { super(16); } public StringBuilder(int capacity) { super(capacity); } public StringBuilder(String str) { super(str.length() + 16); append(str); } public StringBuilder(CharSequence seq) { this(seq.length() + 16); append(seq); }
StringBuilder のデフォルトの容量が 16 であることがわかります。もちろん、初期容量を指定したり、既存の文字シーケンスを使用して StringBuilder オブジェクトに初期値を割り当てることもできます。
2. append() メソッド
public StringBuilder append(String str) { super.append(str); return this; } public StringBuilder append(CharSequence s) { super.append(s); return this; }
append() には多くのオーバーロードされたメソッドがあります。ここでは 2 つを紹介します。明らかに、ここには直接呼び出される親クラス AbstractStringBuilder のメソッドがあります。
3. toString()
public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
toString() メソッドは、元のオブジェクトとメモリを共有しない新しい String オブジェクトを返します。実際、AbstractStringBuilder の subString() メソッドにも同じことが当てはまります。
4. SringBuffer
StiringBuffer は StringBuilder に似ていますが、同期を実現するために、次のメソッドのように多くのメソッドが lSynchronized で変更されている点が異なります。メソッドの前にあります。
さらに、上記の append() メソッドと setLength() メソッドには変数 toStringCache があります。この変数は、StringBuffer が変更されるたびに、最新の toString() メソッドのキャッシュに使用され、null の値が割り当てられます。 StringBuffer の toString は以下の通りです:public synchronized int length() { return count; } public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } public synchronized void setLength(int newLength) { toStringCache = null; super.setLength(newLength); }
このメソッドでは、toStringCache が null の場合、最初にキャッシュされます。最終的に返される String オブジェクトは少し異なります。このコンストラクターにはパラメーター true もあります。 String のソース コードを見つけて見てください:
public synchronized String toString() { if (toStringCache == null) { toStringCache = Arrays.copyOfRange(value, 0, count); } return new String(toStringCache, true); }