web-dev-qa-db-ja.com

Java CharAt()およびdeleteCharAt()のパフォーマンス

JavaのString/StringBuilder/StringBufferのcharAt関数の実装について疑問に思っていましたが、その複雑さは何ですか?StringBuffer/StringBuilderのdeleteCharAt()についてもどうですか?

23
Jimmar

StringStringBuffer、およびStringBuilderの場合、charAt()は定数時間操作です。

StringBufferおよびStringBuilderの場合、deleteCharAt()は線形時間演算です。

StringBufferStringBuilderのパフォーマンス特性は非常に似ています。主な違いは、前者はsynchronized(スレッドセーフ)であるのに対し、後者はそうでないことです。

34
Matt Ball

これらの各メソッドに対応する実際のJava実装(関連コードのみ))を順番に見てみましょう。それ自体が効率について答えます。

String.charAt:

public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

ご覧のとおり、これは定数時間操作である単一の配列アクセスです。

StringBuffer.charAt:

public synchronized char charAt(int index) {
  if ((index < 0) || (index >= count))
    throw new StringIndexOutOfBoundsException(index);
  return value[index];
}

繰り返しますが、単一の配列アクセスなので、一定時間操作です。

StringBuilder.charAt:

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メソッドは、実装をその親クラス自体に委任しています。

StringBuffer.deleteCharAt:

  public synchronized StringBuffer deleteCharAt(int index) {
        super.deleteCharAt(index);
        return this;
    }

StringBuilder.deleteCharAt:

 public StringBuilder deleteCharAt(int index) {
        super.deleteCharAt(index);
        return this;
    }

AbstractStringBuilder.deleteCharAt:

  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)時間の複雑さです。

24

charAtメソッドはO(1)です。

deleteCharAt文字とStringBuilder/StringBufferからランダムな文字を削除する場合、NおよびStringBufferStringBuilderメソッドは平均でO(N)です。 (平均して、削除された文字によって残された「穴」を埋めるために残りの文字の半分を移動する必要があります。複数の操作での償却なしがあります。以下を参照してください。)ただし、最後の文字を削除すると、コストはO(1)になります。

deleteCharAtにはStringメソッドはありません。


理論的には、StringBuilderおよびStringBufferは、バッファーを介して「パス」で複数の文字を挿入または削除する場合に最適化できます。バッファ内のオプションの「ギャップ」を維持し、その間で文字を移動することにより、これを行うことができます。 (IIRC、emacsはこの方法でテキストバッファーを実装します。)このアプローチの問題は次のとおりです。

  • ギャップがどこにあるかを示す属性とギャップ自体のために、より多くのスペースが必要です。
  • これにより、コードがはるかに複雑になり、他の操作が遅くなります。たとえば、charAtoffsetをギャップの開始点と終了点と比較し、文字配列要素をフェッチする前に実際のインデックス値に対応する調整を行う必要があります。
  • アプリケーションが同じバッファで複数の挿入/削除を行う場合にのみ役立ちます。

当然のことながら、この「最適化」は標準のStringBuilder/StringBufferクラスには実装されていません。ただし、カスタムCharSequenceクラスはこのアプローチを使用できます。

7
Stephen C

charAtは超高速で(文字列の組み込み関数を使用できます)、配列への単純なインデックスです。 deleteCharAtはarraycopyを必要とするため、charの削除は高速ではありません。

5
bestsss

文字列はJDKでrandomAccessインターフェースを実装する文字配列として実装されていることは誰もが知っているからです。したがって、charAtの時間の複雑さはint O(1)になるはずです。他の配列と同様に、削除操作にはO(n)時間の複雑さがあります。

0
Elias