質問

ログ出力などのために常に String を構築する必要があります。JDK のバージョンを調べて、どのような場合に使用すべきかを学びました。 StringBuffer (多数の追加、スレッドセーフ) および StringBuilder (追加が多く、スレッドセーフではありません)。

使用上のアドバイスは何ですか String.format()?それは効率的ですか? それとも、パフォーマンスが重要なワンライナーでは連結に固執せざるを得ませんか?

例えば醜い古いスタイル、

String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";

対きちんとした新しいスタイル (String.format、おそらく遅い)、

String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);

注記:私の具体的な使用例は、コード全体にわたる何百もの「ワンライナー」ログ文字列です。ループが含まれていないため、 StringBuilder 重量級すぎる。私は興味を持っている String.format() 具体的には。

役に立ちましたか?

解決

私は2つの優れた性能を持ち、+先行形式の到来するテストするための小さなクラスを書きました。 5〜6倍。

あなたの自己それをお試しください
import java.io.*;
import java.util.Date;

public class StringTest{

    public static void main( String[] args ){
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;

    for( i = 0; i< 100000; i++){
        String s = "Blah" + i + "Blah";
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<100000; i++){
        String s = String.format("Blah %d Blah", i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    }
}

異なるNについては、上記を実行すると、両方が直線的に振る舞うことを示しているが、5〜30倍遅くString.formatです。

の理由は、現在の実装String.formatの最初の正規表現で入力を解析した後、パラメータを埋めることです。プラスとの連結は、他の一方で、(ないJITによって)javacが最適化され、直接StringBuilder.appendを使用します。

ランタイム比較

他のヒント

私が取った ハフェズ コードを追加して、 記憶テスト:

private static void test() {
    Runtime runtime = Runtime.getRuntime();
    long memory;
    ...
    memory = runtime.freeMemory();
    // for loop code
    memory = memory-runtime.freeMemory();

これを、「+」演算子、String.format、StringBuilder (toString() の呼び出し) というアプローチごとに個別に実行するので、使用されるメモリは他のアプローチの影響を受けません。さらに連結を追加して、文字列を "Blah" + i + "Blah"+ i +"Blah" + i + "Blah" にしました。

結果は次のとおりです (それぞれ 5 回の実行の平均)。
アプローチ 時間(ミリ秒) 割り当てられたメモリ (長い)
「+」演算子 747 320,504
文字列.形式 16484 373,312
StringBuilder 769 57,344

String '+' と StringBuilder は時間的には実質的に同じですが、メモリ使用量では StringBuilder の方がはるかに効率的であることがわかります。これは、十分に短い間隔内に多くのログ呼び出し (または文字列を含むその他のステートメント) があり、ガベージ コレクターが「+」演算子の結果として生じる多数の文字列インスタンスをクリーンアップできない場合に非常に重要です。

ところで、ログを確認することを忘れないでください。 レベル メッセージを作成する前に。

結論:

  1. 今後も StringBuilder を使い続けます。
  2. 時間が多すぎるか、人生が短すぎる。

ここで紹介するすべてのベンチマークは、いくつかのの欠陥を持っています、これの結果は信頼できません。

私は誰もがベンチマークのために JMH を使用していないことに驚きましたので、私はやりましたます。

結果:

Benchmark             Mode  Cnt     Score     Error  Units
MyBenchmark.testOld  thrpt   20  9645.834 ± 238.165  ops/s  // using +
MyBenchmark.testNew  thrpt   20   429.898 ±  10.551  ops/s  // using String.format

単位は1秒あたりの操作は、より多くの、より良い、です。 ベンチマークソースコードする。 OpenJDKのののIcedTea 2.5.4 Java仮想マシンが使用された。

だから、(+を使用して)古いスタイルがはるかに高速です。

古い醜いスタイルは、JAVAC 1.6 によって次のように自動的にコンパイルされます。

StringBuilder sb = new StringBuilder("What do you get if you multiply ");
sb.append(varSix);
sb.append(" by ");
sb.append(varNine);
sb.append("?");
String s =  sb.toString();

したがって、これと StringBuilder を使用することの間にはまったく違いはありません。

String.format は、新しい Formatter を作成し、入力フォーマット文字列を解析し、S​​tringBuilder を作成し、それにすべてを追加して toString() を呼び出すため、はるかに軽量です。

Java の String.format は次のように機能します。

  1. フォーマット文字列を解析し、フォーマット チャンクのリストに分解します。
  2. フォーマット チャンクを繰り返し、StringBuilder にレンダリングします。StringBuilder は基本的に、新しい配列にコピーすることで、必要に応じてサイズを変更する配列です。最終的な文字列をどのくらいの大きさに割り当てるかまだわからないため、これが必要です。
  3. StringBuilder.toString() は内部バッファを新しい String にコピーします

このデータの最終宛先がストリームの場合 (例:Web ページのレンダリングやファイルへの書き込みなど)、フォーマット チャンクをストリームに直接組み立てることができます。

new PrintStream(outputStream, autoFlush, encoding).format("hello {0}", "world");

オプティマイザがフォーマット文字列の処理を最適化してくれるのではないかと推測しています。そうであれば、同等のものが残ります 償却された String.format を StringBuilder に手動で展開する場合のパフォーマンスが向上します。

上記の最初の答えに正解/展開するには、それは実際には、String.Formatのはで役立つだろう訳ではありません。
String.Formatのは助けになるだろう何があなたがローカライズがある日付/時刻(または数値形式など)を、印刷しているとき(L10N)の違いは(すなわち、いくつかの国が04Feb2009と他の人がFeb042009を印刷します印刷します)です。
翻訳で、あなたはちょうどあなたがのResourceBundleとのMessageFormatを使用して、右の言語のための右のバンドルを使用できるように、プロパティ・バンドルに(エラーメッセージと何-ないように)任意の外部化文字列を移動する話をしている。

上記のすべてを見て、私はその性能面、String.Formatの対平野連結はあなたが好むものに尽きると思います。あなたはすべての手段によって、その後、連結上.formatへの呼び出しを見たい場合は、それで行きます。
すべての後、コードはそれが書かれているよりも多く読まれています。

あなたの例では、パフォーマンスがprobalbyあまりにも違いはありませんが、考慮すべき他の問題があります。つまり、メモリの断片化が。でも、その一時的に(それをGCに時間がかかり、それはより多くの仕事だ)しても、操作は新しい文字列を作成して連結します。 String.Formatの()は、単により読みやすいですし、それはあまり断片化を伴うます。

あなたは多くのことを、特定のフォーマットを使用している場合は、

また、あなたは(String.Formatの()があるんすべて1つの使用フォーマッタのインスタンスをインスタンス化)を直接フォーマッタ()クラスを使用することができます忘れないでください。

また、何か他のものは、あなたが知っておくべきこと:部分文字列を使用しての注意が必要()。たとえばます:

String getSmallString() {
  String largeString = // load from file; say 2M in size
  return largeString.substring(100, 300);
}

それがどれだけのJavaストリングの仕事だから、大きな文字列はメモリに残っていることを。より良いバージョンがあります:

  return new String(largeString.substring(100, 300));

または

  return String.format("%s", largeString.substring(100, 300));

あなたは、同時に他のものをやっている場合は、2番目の形式は、おそらくより有用である。

それは比較的高速だと、それは(あなたが実際にユーザによって読み取られる何かを書くしようとしていると仮定して)グローバル化をサポートしているので、

一般的に、あなたはString.Formatのを使用する必要があります。また、あなたが(特に大幅に異なる文法構造を持つ言語用)の文あたり3以上対1つの文字列を変換しようとしている場合、それは簡単にグローバル化できるようになります。

は、今、あなたは何を翻訳することを計画したことがない場合は、どちらかは、JavaのStringBuilderに+演算子の変換に建てに依存しています。または明示的にJavaのStringBuilderを使用します。

ロギングの観点のみからの別の視点。

このスレッドではログに関する議論がたくさん行われているので、私の経験を回答に加えてみようと思いました。誰かが役立つと思うかもしれません。

フォーマッタを使用してログを記録する動機は、文字列の連結を避けることにあると思います。基本的に、文字列連結をログに記録しない場合は、文字列連結のオーバーヘッドが発生することは望ましくありません。

ログを記録したくない場合は、実際に連結/フォーマットする必要はありません。このようなメソッドを定義するとします

public void logDebug(String... args, Throwable t) {
    if(debugOn) {
       // call concat methods for all args
       //log the final debug message
    }
}

このアプローチでは、デバッグ メッセージで debugOn = false の場合、cancat/フォーマッタは実際にはまったく呼び出されません。

ただし、ここではフォーマッタの代わりに StringBuilder を使用する方が良いでしょう。主な動機は、そのいずれかを回避することです。

同時に、ログステートメントごとに「if」ブロックを追加するのは好きではありません。

  • 可読性に影響します
  • 単体テストの対象範囲が減ります。すべての行がテストされていることを確認したい場合、これは混乱を招きます。

したがって、私は上記のようなメソッドを使用してロギング ユーティリティ クラスを作成し、パフォーマンスの低下やそれに関連するその他の問題を心配することなく、どこでもそれを使用することを好みます。

hhafez のテストを変更して StringBuilder を含めたところです。XP 上の jdk 1.6.0_10 クライアントを使用すると、StringBuilder は String.format より 33 倍高速になります。-server スイッチを使用すると、係数が 20 に下がります。

public class StringTest {

   public static void main( String[] args ) {
      test();
      test();
   }

   private static void test() {
      int i = 0;
      long prev_time = System.currentTimeMillis();
      long time;

      for ( i = 0; i < 1000000; i++ ) {
         String s = "Blah" + i + "Blah";
      }
      time = System.currentTimeMillis() - prev_time;

      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         String s = String.format("Blah %d Blah", i);
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         new StringBuilder("Blah").append(i).append("Blah");
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);
   }
}

これは大胆に聞こえるかもしれませんが、絶対数がかなり低いため、これが関連するのはまれなケースであると考えています。100 万回の単純な String.format 呼び出しに対して 4 秒は、ロギングなどに使用する限り、ある程度は問題ありません。

アップデート: コメントで sjbotha が指摘したように、最後の StringBuilder テストは無効です。 .toString().

正しい高速化係数 String.format(.)StringBuilder 私のマシンでは 23 です ( -server スイッチ)。

ここでは、hhafezエントリのバージョンが変更されます。これは、文字列ビルダオプションが含まれます。

public class BLA
{
public static final String BLAH = "Blah ";
public static final String BLAH2 = " Blah";
public static final String BLAH3 = "Blah %d Blah";


public static void main(String[] args) {
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;
    int numLoops = 1000000;

    for( i = 0; i< numLoops; i++){
        String s = BLAH + i + BLAH2;
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<numLoops; i++){
        String s = String.format(BLAH3, i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<numLoops; i++){
        StringBuilder sb = new StringBuilder();
        sb.append(BLAH);
        sb.append(i);
        sb.append(BLAH2);
        String s = sb.toString();
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

}

}

ループ391のための後の

時間 ループ4163のための後の時間 時間後にループ227のための

これへの答えはあなたの特定のJavaコンパイラが生成したバイトコードを最適化する方法に非常に依存します。文字列は、理論的には、それぞれ「+」の操作は、新しいものを作成することができ、不変であると。しかし、あなたのコンパイラは、ほぼ確実に長い文字列を構築する上で、中間のステップを離れて最適化します。これは、コードの両方の行が上記まったく同じバイトコードを生成することは完全に可能性があります。

知るための唯一の現実的な方法は、現在の環境で繰り返しコードをテストすることです。繰り返し文字列の両方の方法を連結QDアプリを書いて、互いに対してどのように彼らは時間を見ています。

連結内の文字列の数が少ないため"hello".concat( "world!" )を使用することを検討してください。これは、他のアプローチよりもパフォーマンスのためにも良いかもしれません。

あなたが3つの以上の文字列を持っている場合は、使用コンパイラによっては、StringBuilderのか、単に文字列を使用します。

考えるよりも、
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top