234

ログ出力などのために常に文字列を構築する必要があります。JDK のバージョンStringBufferを通じて、(多くの追加、スレッド セーフ) およびStringBuilder(多くの追加、非スレッド セーフ) をいつ使用するかを学びました。

を使用する際のアドバイスは何String.format()ですか? それは効率的ですか、それともパフォーマンスが重要なワンライナーの連結に固執することを余儀なくされていますか?

たとえば、醜い古いスタイル、

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

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

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

注: 私の特定の使用例は、私のコード全体の何百もの「ワンライナー」ログ文字列です。ループを含まないので、StringBuilder重すぎます。特に興味がありString.format()ます。

4

13 に答える 13

249

私はhhafezコードを取り、メモリテストを追加しました:

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回の実行)。
アプローチ時間(ミリ秒)割り当てられたメモリ(長い)
'+'演算子747320,504
String.format 16484 373,312
StringBuilder 769 57,344

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

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

結論:

  1. StringBuilderを使い続けます。
  2. 私には時間が多すぎるか、人生が少なすぎます。
于 2009-08-15T11:03:05.757 に答える
21

古い醜いスタイルは、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() を呼び出すため、より重いものです。

于 2011-06-29T14:34:33.080 に答える
12

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

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

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

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

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

于 2011-08-20T03:36:46.470 に答える
8

上記の最初の回答を拡張/修正するには、実際には String.format が役立つのは翻訳ではありません。
String.format が役立つのは、ローカリゼーション (l10n) の違いがある日付/時刻 (または数値形式など) を印刷する場合です (つまり、一部の国では 04Feb2009 が印刷され、他の国では Feb042009 が印刷されます)。
翻訳では、ResourceBundle と MessageFormat を使用して、適切な言語に適切なバンドルを使用できるように、外部化可能な文字列 (エラー メッセージなど) をプロパティ バンドルに移動することについて話しているだけです。

上記のすべてを見ると、パフォーマンスに関しては、 String.format と単純な連結のどちらが好みかで決まると思います。連結よりも .format の呼び出しを確認したい場合は、必ずそれを使用してください。
結局のところ、コードは書かれるよりも読まれることが多いのです。

于 2009-02-05T00:09:56.517 に答える
7

あなたの例では、パフォーマンスの確率にそれほど違いはありませんが、考慮すべき他の問題があります。つまり、メモリの断片化です。一時的なものであっても、連結操作でさえ新しい文字列を作成しています(GCに時間がかかり、より多くの作業が必要です)。String.format() は読みやすく、断片化が少なくなります。

また、特定のフォーマットを頻繁に使用している場合は、Formatter() クラスを直接使用できることを忘れないでください (String.format() が行うのは、1 回使用する Formatter インスタンスをインスタンス化することだけです)。

また、注意すべき点が他にもあります: substring() の使用には注意してください。例えば:

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 番目の形式は、同時に他の作業を行っている場合に便利です。

于 2009-02-04T22:18:24.153 に答える
5

一般的には String.Format を使用する必要があります。これは比較的高速であり、グローバリゼーションをサポートしているためです (ユーザーが読み取るものを実際に作成しようとしている場合)。また、ステートメントごとに 1 つの文字列を翻訳しようとする場合と、3 つ以上の文字列を翻訳しようとしている場合 (特に文法構造が大幅に異なる言語の場合) に、グローバル化が容易になります。

何も翻訳する予定がない場合は、Java に組み込まれている + 演算子から への変換に頼ってStringBuilderください。または、Java をStringBuilder明示的に使用します。

于 2009-02-04T22:14:41.587 に答える
3

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

このスレッドへのログインに関連する多くの議論が見られるので、私の経験を回答に追加することを考えました. 誰かがそれを便利だと思うかもしれません。

フォーマッタを使用してログを記録する動機は、文字列の連結を避けることにあると思います。基本的に、ログに記録しないのであれば、string concat のオーバーヘッドは必要ありません。

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

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

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

ただし、ここではフォーマッターの代わりに StringBuilder を使用することをお勧めします。主な動機は、それを避けることです。

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

  • 読みやすさに影響する
  • 単体テストのカバレッジを減らします-すべての行がテストされていることを確認したい場合は混乱します。

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

于 2015-05-21T20:05:28.817 に答える
2

StringBuilder を含めるように hhafez のテストを修正しました。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);
   }
}

これは劇的に聞こえるかもしれませんが、絶対数がかなり少ないため、まれなケースにのみ関連すると考えています.お気に入り。

更新:コメントで sjbotha が指摘したように、 StringBuilder テストは final がないため無効.toString()です。

String.format(.)からへの正しいスピードアップ係数は、私のマシンでは 23 です (スイッチStringBuilderでは 16 )。-server

于 2009-02-04T23:00:08.560 に答える
0

これに対する答えは、特定の Java コンパイラが生成するバイトコードをどのように最適化するかに大きく依存します。文字列は不変であり、理論的には、「+」操作ごとに新しい文字列を作成できます。ただし、コンパイラは、長い文字列を作成する際の中間ステップをほぼ確実に最適化します。上記の両方のコード行がまったく同じバイトコードを生成する可能性は十分にあります。

実際に知る唯一の方法は、現在の環境でコードを繰り返しテストすることです。文字列を双方向に繰り返し連結する QD アプリを作成し、相互にタイムアウトする方法を確認します。

于 2009-02-04T22:16:05.703 に答える
0

"hello".concat( "world!" )連結内の少数の文字列に使用することを検討してください。他のアプローチよりもパフォーマンスが向上する可能性があります。

3 つ以上の文字列がある場合は、使用するコンパイラに応じて、StringBuilder または String のみを使用することを検討してください。

于 2016-12-05T17:10:19.453 に答える