在Java中的字符串连接上使用String.format是更好的做法吗?


273

String.format在Java中,使用和字符串串联之间有明显的区别吗?

我倾向于使用,String.format但偶尔会滑动并使用串联。我想知道一个是否比另一个更好。

我的观察方式String.format为您提供了“格式化”字符串的更多功能;并置意味着您不必担心意外地放入额外的%s或丢失其中一个。

String.format 也较短。

哪个更易读取决于您的头部如何工作。


我认为我们可以使用MessageFormat.format。请参阅答案stackoverflow.com/a/56377112/1491414了解更多信息。
加内萨(Janesa Vijayakumar)

Answers:


242

我建议使用是一种更好的做法String.format()。主要原因是String.format()可以更轻松地使用从资源文件加载的文本进行本地化,而如果不为每种语言生成具有不同代码的新可执行文件,则无法对串联进行本地化。

如果您计划使应用程序可本地化,则还应养成为格式令牌指定参数位置的习惯:

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

然后可以对其进行本地化,并交换名称和时间标记,而无需重新编译可执行文件以说明不同的顺序。使用参数位置,您还可以重复使用相同的参数,而无需两次将其传递给函数:

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

1
您能否指向我一些文档,该文档讨论如何在Java中处理参数位置/顺序(即,如何通过其位置引用参数)?谢谢。
markvgti

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和String Concatenation),但是您是否执行过有关String Builder的数据?
伊卡洛(Icaro)2012年

108
@AmirRaminar:编译器自动将“ +”转换为对StringBuilder的调用。
马丁·施罗德(MartinSchröder)

40
@MartinSchröder:如果运行javap -c StringTest.class,则当您不在循环中时,编译器才会自动将“ +”转换为StringBuilder 。如果连接全部在一行上完成,则与使用'+'相同,但是如果使用myString += "morechars";myString += anotherString;在多行上进行连接,您会注意到可能会创建多个StringBuilder,因此使用“ +”并不总是那么有效作为StringBuilder。
ccpizza 2014年

7
@Joffrey:我的意思是for循环+不会转换为StringBuilder.append(),而是new StringBuilder()在每次迭代中发生。
ccpizza

39

既然有关于性能的讨论,我想我会添加一个包含StringBuilder的比较。实际上,它比concat和String.format选项自然要快。

为了使这种比较苹果我比较苹果,我在循环而不是外部实例化一个新的StringBuilder(这实际上比只进行一次实例化要快,这是由于在循环结束时为循环附加空间分配了额外的开销)一位建造者)。

    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信息[TestMain]-格式= 1416毫秒
  • 2012-01-11 16:30:46,190信息[TestMain]-串联= 134毫秒
  • 2012-01-11 16:30:46,313信息[TestMain]-字符串生成器= 117毫秒

21
StringBuilder测试不会调用toString(),因此这不是一个公平的比较。我怀疑如果修复该错误,您会发现它在连接性能的测量误差之内。
杰米·夏普

15
在串联和格式测试中,您要求输入String。公平地说,StringBuilder测试需要最后一步,将StringBuilder的内容转换为String。您可以通过致电来做到这一点bldString.toString()。我希望能解释吗?
Jamey Sharp

4
Jamey Sharp完全正确。如果不慢于字符串连接,则调用bldString.toString()大约相同。
Akos Cz 2013年

3
String s = bldString.toString(); 定时用级联和StringBuilder的几乎看齐,与对方: Format = 1520 millisecondConcatenation = 167 millisecondString Builder = 173 millisecond 我跑他们在一个循环中,平均每个出去找一个好名声:(预JVM的优化,将尝试10000+循环时,我得到的时间)
TechTrip 2014年

3
你们甚至如何知道代码是否完全执行?这些变量永远不会被读取或使用,您不能确保JIT首先不会删除此代码。
alobodzk '17

37

问题之一.format是您失去了静态类型安全性。您的格式参数可能太少,格式说明符的类型也可能错误-两者都导致一个IllegalFormatException at runtime,因此最终可能会导致记录代码破坏生产。

相反,to的参数+可以由编译器测试。

安全历史(在其format上建模函数)冗长而令人恐惧。


16
仅作记录,现代IDE(例如IntelliJ)可协助进行参数计数和类型匹配
Ron Klein

2
关于编译的要点,我建议您通过FindBugs(可以在IDE中运行或在构建过程中通过Maven运行)进行这些检查,请注意,这还将检查所有日志记录中的格式!无论用户使用什么IDE,此方法都有效
Christophe Roussy

20

哪个更易读取决于您的头部如何工作。

您在那里得到了答案。

这是个人喜好问题。

我想,字符串连接的速度稍快一些,但这应该可以忽略不计。


3
我同意。在这里考虑性能差异主要是过早的优化-在不太可能的情况下,分析显示这里存在问题,然后再担心。
约尼克,

3
如果这个项目很小,并且从不打算以任何有意义的方式进行国际化,那实际上只是个人喜好问题。否则,String.format会以各种方式胜过串联。
workmad3

4
我不同意。无论项目有多大,您几乎都不会本地化其中已构造的每个字符串。换句话说,它取决于情况(用于什么字符串)。
约尼克,

我无法想象有人会认为'String.format(“%s%s”,a,b)'比'a + b'更具可读性,并且考虑到速度的数量级差异,对我来说,答案似乎很清楚(在不需要本地化的情况下,例如调试或大多数日志记录语句)。
BobDoolittle '17

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
当您在循环中追加字符时,例如,当您想要通过逐个添加一个带有一个1的字符串来创建字符串时,StringBuilder绝对是最快的方法。下面是详细信息:pellegrino.link/2015/08/22/...
卡洛斯·霍约斯

我喜欢您始终使用String.format进行输出:D的方式,因此有一个优势。老实说,如果我们不谈论数百万的烦恼,我更喜欢string.format来提高可读性,因为您的代码显示出明显的优势!
mohamnag

9

这是与上述相同的测试,只是修改了对StringBuildertoString()方法的调用。下面的结果表明,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");

  }
}

Shell命令:(编译并运行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。后者有两个主要缺点:

  1. 它不对要以本地方式构建的字符串进行编码。
  2. 构建过程被编码为字符串。

到第1点,我的意思是不可能String.format()一次连续地了解一次呼叫在做什么。一个人被迫在格式字符串和参数之间来回移动,同时计算参数的位置。对于短串联,这不是什么大问题。但是,在这些情况下,字符串连接不太冗长。

在第2点,我的意思是构建过程的重要部分以格式字符串(使用DSL)进行编码。使用字符串表示代码有很多缺点。它本身不是类型安全的,并且使语法突出显示,代码分析,优化等复杂化。

当然,当使用Java语言外部的工具或框架时,新因素会起作用。


2

我没有做任何特定的基准测试,但是我认为串联可能会更快。String.format()创建一个新的Formatter,后者又创建一个新的StringBuilder(大小仅为16个字符)。这是相当大的开销,尤其是当您格式化较长的字符串并且StringBuilder不得不调整大小时。

但是,级联的作用较小,更难阅读。与往常一样,值得对您的代码进行基准测试,看看哪个更好。在将资源包,语言环境等加载到内存中并且对代码进行JITted处理后,在服务器应用程序中的差异可以忽略不计。

也许作为最佳实践,最好使用大小合适的StringBuilder(附加)和Locale创建自己的Formatter,并在需要进行大量格式化的情况下使用它。


2

可能会有明显的差异。

String.format 非常复杂,并且在下面使用了正则表达式,因此不要习惯在任何地方使用它,而是只在需要时使用它。

StringBuilder 将会快一个数量级(就像这里已经指出的那样)。


1

我认为我们可以接受,MessageFormat.format因为它在可读性和性能方面都应该很好。

我使用了与Icaro在上面的答案中使用的程序相同的程序,并通过附加代码来增强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毫秒
格式:curly-brackets = 416毫秒
MessageFormat = 215毫秒
MessageFormat:curly-brackets = 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
您是否尝试过代码?串联总是快十倍
Icaro 2011年

关于执行此“ System.currentTimeMillis()”所用的毫秒数呢:P。
热汗

0

习惯一点时间来适应String.Format,但是在大多数情况下还是值得的。在NRA(永远不要重复任何内容)的世界中,将标记化的消息(日志或用户)保存在Constant库中(我更喜欢静态类),然后根据需要使用String.Format调用它们非常有用,无论您是否是否本地化。尝试将这种库与串联方法一起使用,很难通过任何需要串联的方法来进行读取,故障排除,校对和管理。更换是一种选择,但我怀疑它的性能。经过多年的使用,我遇到的最大问题是String.Format,当我将其传递给另一个函数(例如Msg)时,调用的长度很长,但是使用自定义函数作为别名很容易解决。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.