JavaのString/StringBuilder/StringBufferのcharAt関数の実装について疑問に思っていましたが、その複雑さは何ですか?StringBuffer/StringBuilderのdeleteCharAt()についてもどうですか?
String
、StringBuffer
、およびStringBuilder
の場合、charAt()
は定数時間操作です。
StringBuffer
およびStringBuilder
の場合、deleteCharAt()
は線形時間演算です。
StringBuffer
とStringBuilder
のパフォーマンス特性は非常に似ています。主な違いは、前者はsynchronized
(スレッドセーフ)であるのに対し、後者はそうでないことです。
これらの各メソッドに対応する実際のJava実装(関連コードのみ))を順番に見てみましょう。それ自体が効率について答えます。
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
ご覧のとおり、これは定数時間操作である単一の配列アクセスです。
public synchronized char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
繰り返しますが、単一の配列アクセスなので、一定時間操作です。
public char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
繰り返しますが、単一の配列アクセスなので、一定時間操作です。これら3つの方法はすべて同じに見えますが、いくつかの小さな違いがあります。たとえば、StringBuffer.charAtメソッドのみが同期され、他のメソッドは同期されません。同様にチェックの場合は、String.charAtの場合は少し異なります(理由を推測してください)。これらのメソッドの実装自体をよく見ると、他の小さな違いがあります。
次に、deleteCharAtの実装を見てみましょう。
文字列にはdeleteCharAtメソッドがありません。理由は、それが不変オブジェクトである可能性があります。したがって、このメソッドがオブジェクトを変更することを明示的に示すAPIを公開することは、おそらく良い考えではありません。
StringBufferとStringBuilderはどちらもAbstractStringBuilderのサブクラスです。これら2つのクラスのdeleteCharAtメソッドは、実装をその親クラス自体に委任しています。
public synchronized StringBuffer deleteCharAt(int index) {
super.deleteCharAt(index);
return this;
}
public StringBuilder deleteCharAt(int index) {
super.deleteCharAt(index);
return this;
}
public AbstractStringBuilder deleteCharAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
System.arraycopy(value, index+1, value, index, count-index-1);
count--;
return this;
}
AbstractStringBuilder.deleteCharAtメソッドを詳しく見ると、それが実際にSystem.arraycopyを呼び出していることがわかります。これはO(N)最悪の場合)になる可能性があります。したがって、deleteChatAtメソッドはO(N)時間の複雑さです。
charAt
メソッドはO(1)
です。
deleteCharAt
文字とStringBuilder
/StringBuffer
からランダムな文字を削除する場合、N
およびStringBuffer
のStringBuilder
メソッドは平均でO(N)
です。 (平均して、削除された文字によって残された「穴」を埋めるために残りの文字の半分を移動する必要があります。複数の操作での償却なしがあります。以下を参照してください。)ただし、最後の文字を削除すると、コストはO(1)
になります。
deleteCharAt
にはString
メソッドはありません。
理論的には、StringBuilder
およびStringBuffer
は、バッファーを介して「パス」で複数の文字を挿入または削除する場合に最適化できます。バッファ内のオプションの「ギャップ」を維持し、その間で文字を移動することにより、これを行うことができます。 (IIRC、emacsはこの方法でテキストバッファーを実装します。)このアプローチの問題は次のとおりです。
charAt
はoffset
をギャップの開始点と終了点と比較し、文字配列要素をフェッチする前に実際のインデックス値に対応する調整を行う必要があります。当然のことながら、この「最適化」は標準のStringBuilder
/StringBuffer
クラスには実装されていません。ただし、カスタムCharSequence
クラスはこのアプローチを使用できます。
charAt
は超高速で(文字列の組み込み関数を使用できます)、配列への単純なインデックスです。 deleteCharAt
はarraycopyを必要とするため、charの削除は高速ではありません。
文字列はJDKでrandomAccess
インターフェースを実装する文字配列として実装されていることは誰もが知っているからです。したがって、charAt
の時間の複雑さはint O(1)になるはずです。他の配列と同様に、削除操作にはO(n)
時間の複雑さがあります。