7

私はJavaを学んでおり、ここで文字列を変更する最良の方法は何だろうと思っています(パフォーマンスとJavaで推奨される方法の両方を学ぶため)。文字列をループして各文字をチェックしている、または文字列内のそのインデックスに対して何らかのアクションを実行しているとします。

クラスを使用しますStringBuilderか、それとも文字列を char 配列に変換し、変更を加えてから、char 配列を文字列に変換し直しますか?

StringBuilder:

StringBuilder newString = new StringBuilder(oldString);
for (int i = 0; i < oldString.length() ; i++) {
    newString.setCharAt(i, 'X');    
}

文字配列変換の例:

char[] newStringArray = oldString.toCharArray();
for (int i = 0; i < oldString.length() ; i++) {
    myNameChars[i] = 'X';    
}    
myString = String.valueOf(newStringArray);

それぞれの方法の長所と短所は何ですか?

StringBuilderインデックスを更新するたびに char 配列に変換すると配列のコピーが作成されるため、より効率的であると思います。

4

4 に答える 4

4

文字列の「変更」が速度を低下させていることがわかるまで、最も読みやすく/保守しやすいものは何でも実行してください。私にとって、これは最も読みやすいです:

Sting s = "foo";
s += "bar";
s += "baz";

それが遅すぎる場合は、を使用しStringBuilderます。これを と比較するとStringBufferよいでしょう。パフォーマンスが重要で同期が重要でない場合は、より高速にStringBuilderする必要があります。同期が必要な場合は、 を使用する必要がありますStringBuffer

また、これらの文字列が変更されていないことを知っておくことも重要です。Java では、Strings は不変です。


これはすべてコンテキスト固有です。このコードを最適化しても、目立った違いが見られない場合 (通常はこれが当てはまります)、必要以上に長く考えただけで、コードが理解しにくくなっている可能性があります。できるからではなく、必要なときに最適化してください。その前に、最適化しているコードがパフォーマンスの問題の原因であることを確認してください。

于 2013-10-09T18:16:43.057 に答える
1

どのオプションが最高のパフォーマンスを発揮するかは、簡単な問題ではありません。

Caliperを使用してベンチマークを行いました:

                RUNTIME (NS)
array           88
builder         126
builderTillEnd  76
concat          3435

ベンチマークされた方法:

public static String array(String input)
{
    char[] result = input.toCharArray(); // COPYING
    for (int i = 0; i < input.length(); i++)
    {
        result[i] = 'X';
    }
    return String.valueOf(result); // COPYING
}

public static String builder(String input)
{
    StringBuilder result = new StringBuilder(input); // COPYING
    for (int i = 0; i < input.length(); i++)
    {
        result.setCharAt(i, 'X');
    }
    return result.toString(); // COPYING
}

public static StringBuilder builderTillEnd(String input)
{
    StringBuilder result = new StringBuilder(input); // COPYING
    for (int i = 0; i < input.length(); i++)
    {
        result.setCharAt(i, 'X');
    }
    return result;
}

public static String concat(String input)
{
    String result = "";
    for (int i = 0; i < input.length(); i++) 
    {
        result += 'X'; // terrible COPYING, COPYING, COPYING... same as:
                       // result = new StringBuilder(result).append('X').toString();
    }
    return result;
}

備考

  1. Java の文字列は不変であるため、文字列を変更する場合は、その入力文字列のコピーを少なくとも 1 つ作成する必要があります。

  2. java.lang.StringBuilder伸びjava.lang.AbstractStringBuilderます。StringBuilder.setCharAt()から継承されAbstractStringBuilder、次のようになります。

    public void setCharAt(int index, char ch) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        value[index] = ch;
    }
    

    AbstractStringBuilder内部的に最も単純な char 配列を使用します: char value[]. はresult[i] = 'X'と非常によく似ていresult.setCharAt(i, 'X')ますが、2 番目のメソッドはポリモーフィック メソッド (おそらく JVM によってインライン化される) を呼び出し、 で境界をチェックするifため、少し遅くなります。

結論

  1. 最後まで操作できる場合StringBuilder(String を戻す必要がない場合) - 実行してください。これは好ましい方法であり、最速でもあります。単に最高です。

  2. 最後に String が必要で、これがプログラムのボトルネックである場合は、char 配列の使用を検討してください。ベンチマークでは、char 配列は より ~25% 高速StringBuilderでした。この 25% についての保証はないため、最適化の前後でプログラムの実行時間を適切に測定してください。

  3. 何をするのか本当にわかっていない限り、ループ内で文字列を+orで連結しないでください。+=通常は、明示的なStringBuilderandを使用する方が適切append()です。

于 2013-10-09T18:41:54.447 に答える
1

それぞれの方法の長所と短所は何ですか。インデックスを更新するたびに char 配列に変換すると配列のコピーが作成されるため、 StringBuilder の方が効率的であると思います。

書かれているように、2 番目の例のコードは 2 つの配列のみを作成しtoCharArray()ます。実行している要素の操作によって、オブジェクトの割り当てがトリガーされないようにする必要があります。要素の読み取りまたは書き込み時に、配列のコピーが作成されることはありません。String.valueOf()String

なんらかのString操作を行う場合、推奨される方法はStringBuilder. パフォーマンスが非常に重要なコードを記述していて、変換によって文字列の長さが変わらない場合は、配列を直接操作する価値があるかもしれません。しかし、あなたは新しい言語として Java を学んでいるので、高頻度取引やその他のレイテンシーが重要な環境で働いているわけではないと推測します。したがって、おそらくStringBuilder.

元の長さとは異なる長さの文字列を生成する可能性のある変換を実行している場合は、ほぼ確実にStringBuilder;を使用する必要があります。必要に応じて内部バッファのサイズを変更します。

これに関連して、単純な文字列連結 (例: s = "a" + someObject + "c") を実行している場合、コンパイラは実際にこれらの操作を一連の呼び出しに変換するStringBuilder.append()ので、見た目が良いと思う方を自由に使用できます。個人的には+オペレーターの方が好きです。ただし、複数のステートメントにまたがる文字列を作成する場合は、単一のStringBuilder.

例えば:

public String toString() {
    return "{field1 =" + this.field1 + 
           ",  field2 =" + this.field2 + 
           ...
           ",  field50 =" + this.field50 + "}";
}

ここには、多くの連結を含む単一の長い式があります。これを手動で最適化することについて心配する必要はありません。コンパイラは単一のを使用し、それを繰り返しStringBuilder呼び出すだけだからです。append()

String s = ...;
if (someCondition) {
    s += someValue;
}
s += additionalValue;
return s;

ここでは、内部で 2 つStringBuildersのコードが作成されることになりますが、これがレイテンシ クリティカルなアプリケーションで非常にホットなコード パスでない限り、心配する必要はありません。同様のコードを考えると、より多くの個別の連結がある場合、最適化する価値があるかもしれません。文字列が非常に大きい可能性があることがわかっている場合も同様です。しかし、ただ推測するのではなく、測定してください。修正を試みる前に、パフォーマンスの問題があることを実証します。 (注: これは「マイクロ最適化」の一般的なルールにすぎません。明示的に a を使用することのマイナス面はめったにありStringBuilderません。ただし、測定可能な違いが生じると想定しないでください。心配な場合は、実際に を測定する必要があります。)

String s = "";
for (final Object item : items) {
    s += item + "\n";
}

ここでは、各ループ反復で個別の連結操作を実行しています。つまり、StringBuilder各パスで新しいが割り当てられます。この場合、StringBuilderコレクションのサイズがわからない可能性があるため、おそらくシングルを使用する価値があります。これは、「ルールを最適化する前にパフォーマンスの問題があることを証明する」の例外と考えます。入力に基づいて操作が複雑になる可能性がある場合は、注意してください。

于 2013-10-09T18:28:30.163 に答える
0

StringBuilder元の文字列が変更されたクラスを使用したいと思います。

文字列操作では、 StringUtilクラスが好きです。それを使用するには、Apache commons の依存関係を取得する必要があります

于 2013-10-09T18:21:11.590 に答える