Javaで文字列連結よりもString.formatを使用する方が良いでしょうか?


273

String.formatJavaでのとString連結の使用の間に、目に見える違いはありますか?

私は使用する傾向がありますString.formatが、時々スリップして連結を使用します。私はどちらがより優れているのかと思っていました。

私がそれを見ると、String.format文字列を「フォーマット」する上でより大きな力を与えます。連結することで、誤って余分な%sを入れたり、見落としたりする心配がありません。

String.format また短いです。

どちらがより読みやすいかは、頭の動きによって異なります。


MessageFormat.formatで行けると思います。詳細については、回答stackoverflow.com/a/56377112/1491414を参照してください。
Ganesa Vijayakumar

回答:


242

を使用することをお勧めしますString.format()。主な理由は、String.format()リソースファイルから読み込まれたテキストを使用してローカライズしやすく、言語ごとに異なるコードを使用して新しい実行可能ファイルを作成しないと連結をローカライズできないためです。

アプリをローカライズ可能にする予定がある場合は、フォーマットトークンの引数の位置も指定する習慣をつける必要があります。

"Hello %1$s the time is %2$t"

次に、これをローカライズし、名前と時間のトークンを入れ替えることができます。異なる順序を考慮して実行可能ファイルを再コンパイルする必要はありません。引数の位置を使用すると、同じ引数を関数に2回渡さずに再利用することもできます。

String.format("Hello %1$s, your name is %1$s and the time is %2$t", name, time)

1
Javaで引数の位置/順序を操作する方法(つまり、引数をその位置で参照する方法)について説明しているドキュメントをいくつか教えてもらえますか?ありがとう。
markvgti 2011

13
より良い遅く決してより、ランダムなJavaバージョン:docs.oracle.com/javase/1.5.0/docs/api/java/util/...
Aksel

174

パフォーマンスについて:

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }
  long end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;

  start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = String.format("Hi %s; Hi to you %s",i, + i*2);
  }
  end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
}

タイミングの結果は次のとおりです。

  • 連結= 265ミリ秒
  • 形式= 4141ミリ秒

したがって、連結はString.formatよりもはるかに高速です。


15
それらはすべて悪い習慣です。StringBuilderを使用します。
アミール・ラミンファー

8
StringBuilderはここでは範囲外です(OPの質問はString.formatと文字列連結の比較に関するものでした)が、文字列ビルダーに関するデータを実行しましたか?
イカロ

108
@AmirRaminar:コンパイラーは「+」をStringBuilderの呼び出しに自動的に変換します。
MartinSchröder

40
@MartinSchröder:実行javap -c StringTest.classすると、ループに入っていない場合にのみ、コンパイラーが「+」をStringBuilderに自動的に変換することがわかります。連結がすべて1行で行われる場合は '+'を使用するのと同じですが、複数行で使用するmyString += "morechars";場合myString += anotherString;は複数のStringBuilderが作成される可能性があるため、「+」を使用しても必ずしも効率的ではありませんStringBuilderとして。
ccpizza 14年

7
@Joffrey:私が意味したことは、forループ+が変換されStringBuilder.append()ずに、new StringBuilder()各反復で発生することでした。
ccpizza

39

パフォーマンスについての議論があるので、StringBuilderを含む比較に追加すると考えました。実際、連結よりも高速で、当然String.formatオプションも高速です。

これをリンゴ同士の比較の一種にするために、新しいStringBuilderを外部ではなくループ内でインスタンス化します(これは、ループの最後にスペースを再割り当てするためのスペースを再割り当てするオーバーヘッドが原因で、インスタンス化を1つだけ行うよりも実際には高速です) 1つのビルダー)。

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    log.info("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    log.info("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("; Hi to you ").append(i * 2);
    }

    end = System.currentTimeMillis();

    log.info("String Builder = " + ((end - start)) + " millisecond");
  • 2012-01-11 16:30:46,058 INFO [TestMain]-形式= 1416ミリ秒
  • 2012-01-11 16:30:46,190 INFO [TestMain]-連結= 134ミリ秒
  • 2012-01-11 16:30:46,313 INFO [TestMain]-文字列ビルダー= 117ミリ秒

21
StringBuilderテストはtoString()を呼び出さないため、公平な比較ではありません。そのバグを修正すれば、それは連結のパフォーマンスの測定エラーの範囲内にあることがわかると思います。
Jamey Sharp

15
連結テストとフォーマットテストで、String。StringBuilderテストは、公平を期すために、StringBuilderのコンテンツをStringに変換する最後の手順を必要とします。これは、を呼び出すことで行いますbldString.toString()。それで説明できたらいいのに?
Jamey Sharp

4
ジェイミー・シャープはまさにその通りです。bldString.toString()の呼び出しは、文字列の連結より遅くない場合とほぼ同じです。
Akos Cz

3
String s = bldString.toString(); タイミングが互いにほぼ並みの連結とStringBuilderのであった。 Format = 1520 millisecondConcatenation = 167 millisecondString Builder = 173 millisecond 私はループ内でそれらを実行し、良い担当者を取得するために、各アウトの平均:(私は時間を取得する際の事前JVMの最適化を、10000 +ループをしようとします)
TechTrip 14

3
コードが実行されるかどうかはどうやってわかりますか?変数が読み取られたり使用されたりすることはありません。JITがこのコードを最初から削除していないかどうかはわかりません。
alobodzk

37

問題の1つ.formatは、静的型安全性を失うことです。フォーマットの引数が少なすぎたり、フォーマット指定子のタイプが間違っている可能性があります。どちらもIllegalFormatException 実行時ににつながるため、プロダクションを中断させるコードをログに記録することになります。

対照的に、への引数+はコンパイラーでテストできます。

セキュリティ履歴format関数がモデル化されている)は長くて恐ろしいです。


16
参考までに、最新のIDE(IntelliJなど)は引数のカウントと型のマッチングを支援します
Ron Klein

2
コンパイルの良い点は、これらのチェックをFindBugs(IDEで実行するか、ビルド中にMavenを介して実行できる)で行うことをお勧めします。これにより、すべてのロギングのフォーマットもチェックされます。これはユーザーIDEに関係なく機能します
Christophe Roussy

20

どちらがより読みやすいかは、頭の動きによって異なります。

あなたはすぐそこにあなたの答えを得ました。

それは個人的な好みの問題です。

文字列の連結はわずかに速いと思いますが、それはごくわずかなはずです。


3
同意する。ここでのパフォーマンスの違いについて考えることは、主に時期尚早な最適化です-プロファイリングがここに問題があることを示しているとは思えない場合に、それについて心配します。
Jonik、2009年

3
プロジェクトが小規模で、有意義な意味での国際化を意図していない場合、それは本当に個人的な好みの問題です。それ以外の場合、String.formatはあらゆる方法で連結よりも優れています。
workmad3 2009年

4
同意しません。プロジェクトの規模に関係なく、プロジェクト内で構築されたすべての文字列をローカライズすることはほとんどありません。つまり、状況に応じて異なります(使用する文字列は何ですか)。
Jonik、2009年

誰かが「String.format( "%s%s"、a、b)」を「a + b」よりも読みやすいと考える方法を想像することはできません。答えは私には明らかなようです(デバッグやほとんどのロギングステートメントなどのローカライズが不要な状況)。
BobDoolittle 2017年

16

これは、ミリ秒単位の複数のサンプルサイズを使用したテストです。

public class Time {

public static String sysFile = "/sys/class/camera/rear/rear_flash";
public static String cmdString = "echo %s > " + sysFile;

public static void main(String[] args) {

  int i = 1;
  for(int run=1; run <= 12; run++){
      for(int test =1; test <= 2 ; test++){
        System.out.println(
                String.format("\nTEST: %s, RUN: %s, Iterations: %s",run,test,i));
        test(run, i);
      }
      System.out.println("\n____________________________");
      i = i*3;
  }
}

public static void test(int run, int iterations){

      long start = System.nanoTime();
      for( int i=0;i<iterations; i++){
          String s = "echo " + i + " > "+ sysFile;
      }
      long t = System.nanoTime() - start;   
      String r = String.format("  %-13s =%10d %s", "Concatenation",t,"nanosecond");
      System.out.println(r) ;


     start = System.nanoTime();       
     for( int i=0;i<iterations; i++){
         String s =  String.format(cmdString, i);
     }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "Format",t,"nanosecond");
     System.out.println(r);

      start = System.nanoTime();          
      for( int i=0;i<iterations; i++){
          StringBuilder b = new StringBuilder("echo ");
          b.append(i).append(" > ").append(sysFile);
          String s = b.toString();
      }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "StringBuilder",t,"nanosecond");
     System.out.println(r);
}

}

TEST: 1, RUN: 1, Iterations: 1
  Concatenation =     14911 nanosecond
  Format        =     45026 nanosecond
  StringBuilder =      3509 nanosecond

TEST: 1, RUN: 2, Iterations: 1
  Concatenation =      3509 nanosecond
  Format        =     38594 nanosecond
  StringBuilder =      3509 nanosecond

____________________________

TEST: 2, RUN: 1, Iterations: 3
  Concatenation =      8479 nanosecond
  Format        =     94438 nanosecond
  StringBuilder =      5263 nanosecond

TEST: 2, RUN: 2, Iterations: 3
  Concatenation =      4970 nanosecond
  Format        =     92976 nanosecond
  StringBuilder =      5848 nanosecond

____________________________

TEST: 3, RUN: 1, Iterations: 9
  Concatenation =     11403 nanosecond
  Format        =    287115 nanosecond
  StringBuilder =     14326 nanosecond

TEST: 3, RUN: 2, Iterations: 9
  Concatenation =     12280 nanosecond
  Format        =    209051 nanosecond
  StringBuilder =     11818 nanosecond

____________________________

TEST: 5, RUN: 1, Iterations: 81
  Concatenation =     54383 nanosecond
  Format        =   1503113 nanosecond
  StringBuilder =     40056 nanosecond

TEST: 5, RUN: 2, Iterations: 81
  Concatenation =     44149 nanosecond
  Format        =   1264241 nanosecond
  StringBuilder =     34208 nanosecond

____________________________

TEST: 6, RUN: 1, Iterations: 243
  Concatenation =     76018 nanosecond
  Format        =   3210891 nanosecond
  StringBuilder =     76603 nanosecond

TEST: 6, RUN: 2, Iterations: 243
  Concatenation =     91222 nanosecond
  Format        =   2716773 nanosecond
  StringBuilder =     73972 nanosecond

____________________________

TEST: 8, RUN: 1, Iterations: 2187
  Concatenation =    527450 nanosecond
  Format        =  10291108 nanosecond
  StringBuilder =    885027 nanosecond

TEST: 8, RUN: 2, Iterations: 2187
  Concatenation =    526865 nanosecond
  Format        =   6294307 nanosecond
  StringBuilder =    591773 nanosecond

____________________________

TEST: 10, RUN: 1, Iterations: 19683
  Concatenation =   4592961 nanosecond
  Format        =  60114307 nanosecond
  StringBuilder =   2129387 nanosecond

TEST: 10, RUN: 2, Iterations: 19683
  Concatenation =   1850166 nanosecond
  Format        =  35940524 nanosecond
  StringBuilder =   1885544 nanosecond

  ____________________________

TEST: 12, RUN: 1, Iterations: 177147
  Concatenation =  26847286 nanosecond
  Format        = 126332877 nanosecond
  StringBuilder =  17578914 nanosecond

TEST: 12, RUN: 2, Iterations: 177147
  Concatenation =  24405056 nanosecond
  Format        = 129707207 nanosecond
  StringBuilder =  12253840 nanosecond

1
StringBuilderは、ループで文字を追加する場合、たとえば1を1つずつ追加して1000の1を含む文字列を作成する場合に、最速の方法です。ここでは詳細は以下のとおりです。pellegrino.link/2015/08/22/...
カルロス・オヨス

出力:Dに常にString.formatを使用する方法が好きなので、利点があります。何百万もの発話について話していない場合は正直に言うと、コードには明らかな利点があるため、読みやすさのためにstring.formatを使用します。
mohamnag 2018

9

上記と同じテストですが、StringBuilderでtoString()メソッドを呼び出すことを変更しています。以下の結果は、StringBuilderアプローチが+演算子を使用したString連結よりも少し遅いことを示しています。

ファイル:StringTest.java

class StringTest {

  public static void main(String[] args) {

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("Hi to you ").append(i * 2).toString();
    }

    end = System.currentTimeMillis();

    System.out.println("String Builder = " + ((end - start)) + " millisecond");

  }
}

シェルコマンド:(StringTestを5回コンパイルして実行)

> javac StringTest.java
> sh -c "for i in \$(seq 1 5); do echo \"Run \${i}\"; java StringTest; done"

結果 :

Run 1
Format = 1290 millisecond
Concatenation = 115 millisecond
String Builder = 130 millisecond

Run 2
Format = 1265 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond

Run 3
Format = 1303 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 4
Format = 1297 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 5
Format = 1270 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond

6

String.format()文字列を連結するだけではありません。たとえば、を使用して、特定のロケールで数値を表示できますString.format()

ただし、ローカライズを気にしない場合、機能的な違いはありません。どちらかが他より速いかもしれませんが、ほとんどの場合無視できます。


4

一般に、文字列の連結はよりも優先されString.formatます。後者には2つの主な欠点があります。

  1. ローカルに構築される文字列はエンコードしません。
  2. 構築プロセスは文字列にエンコードされます。

ポイント1 String.format()とは、コールが単一の順次パスで何を行っているかを理解することが不可能であることを意味します。引数の位置を数えながら、フォーマット文字列と引数の間を行き来することを強いられます。短い連結の場合、これはそれほど問題ではありません。ただし、これらの場合、文字列の連結はそれほど冗長ではありません。

ポイント2とは、構築プロセスの重要な部分が(DSLを使用して)フォーマット文字列にエンコードされることを意味します。文字列を使用してコードを表すことには、多くの欠点があります。本質的にタイプセーフではなく、構文の強調表示、コード分析、最適化などが複雑になります。

もちろん、Java言語の外部にあるツールやフレームワークを使用する場合、新しい要素が関係する可能性があります。


2

具体的なベンチマークは行っていませんが、連結の方が速いと思います。String.format()は新しいFormatterを作成し、次にそれが新しいStringBuilder(サイズはわずか16文字)を作成します。これは、特に長い文字列をフォーマットしていて、StringBuilderがサイズ変更しなければならない場合、かなりのオーバーヘッドになります。

ただし、連結はあまり役に立たず、読みにくくなります。いつものように、どちらが良いかを確認するためにコードでベンチマークを行う価値があります。リソースバンドル、ロケールなどがメモリに読み込まれ、コードがJITされた後のサーバーアプリでは、違いはごくわずかです。

多分ベストプラクティスとして、適切なサイズのStringBuilder(追加可能)とロケールを使用して独自のフォーマッタを作成し、多くの書式設定が必要な場合はそれを使用することをお勧めします。


2

知覚可能な違いがあるかもしれません。

String.format は非常に複雑で、その下で正規表現を使用するので、どこでも使用するのではなく、必要な場所でのみ使用するようにしてください。

StringBuilder ここで誰かがすでに指摘したように、1桁速くなります。


1

MessageFormat.format読みやすさとパフォーマンスの両方の点で優れているはずなので、これでうまくいくと思います。

私は、イカロが彼の上記の回答で使用したものと同じプログラムを使用しMessageFormat、パフォーマンス数値を説明するために使用するコードを追加することで、プログラムを拡張しました。

  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = "Hi " + i + "; Hi to you " + i * 2;
    }
    long end = System.currentTimeMillis();
    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = String.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = MessageFormat.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("MessageFormat = " + ((end - start)) + " millisecond");
  }

連結= 69ミリ秒

形式= 1435ミリ秒

MessageFormat = 200ミリ秒

更新:

SonarLintレポートに従って、Printfスタイルのフォーマット文字列を正しく使用する必要があります(squid:S3457)

のでprintf-styleフォーマット文字列は、コンパイラによって、実行時に解釈するのではなく、検証され、彼らは間違った文字列が作成される結果の誤差を含むことができます。このルールは、静的の相関検証printf-styleの形式(...)メソッドを呼び出すときにその引数にフォーマット文字列をjava.util.Formatterjava.lang.Stringjava.io.PrintStreamMessageFormat、およびjava.io.PrintWriterクラスとprintf(...)のメソッドjava.io.PrintStreamjava.io.PrintWriterクラス。

printfスタイルを中括弧で置き換え、以下のような興味深い結果を得ました。

連結= 69ミリ秒
形式= 1107ミリ秒
形式:中括弧= 416ミリ秒
MessageFormat = 215ミリ秒
MessageFormat:中括弧= 2517ミリ秒

私の結論:
上記で強調したように、中かっこでString.formatを使用することは、読みやすさとパフォーマンスの両方の利点を得るのに適した選択肢です。


0

上記のプログラムでは、String ConcatenationとString.Formatを比較することはできません。

以下のように、コードブロックでString.FormatとConcatenationを使用する位置を入れ替えることもできます。

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = String.format( "Hi %s; Hi to you %s",i, + i*2);
  }

  long end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
  start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }

  end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;
}

ここでFormatがより高速に動作することに驚かれることでしょう。これは、作成された最初のオブジェクトが解放されない可能性があり、メモリ割り当てに問題があり、それによってパフォーマンスが低下する可能性があるためです。


3
コードを試しましたか?連結は常に10倍速くなります
Icaro

この「System.currentTimeMillis()」を実行するためにかかるミリ秒数はどうでしょうか:P。
2018年

0

String.Formatに慣れるには少し時間がかかりますが、ほとんどの場合それだけの価値があります。NRA(絶対に何も繰り返さない)の世界では、トークン化されたメッセージ(ログまたはユーザー)を定数ライブラリ(静的クラスと同じものを好む)に保持し、必要に応じてString.Formatでそれらを呼び出すことは非常に便利です。ローカライズ中かどうか。このようなライブラリを連結方式で使用しようとすると、連結が必要なすべてのアプローチで読み取り、トラブルシューティング、校正、および管理が難しくなります。交換はオプションですが、私はそれが高性能であることを疑います。長年使用した後、String.Formatの最大の問題は、別の関数(Msgなど)に渡すときに呼び出しの長さが不便に長くなることですが、カスタム関数をエイリアスとして使用するのは簡単です。 。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.