在Java中,使用String.format比字符串拼接更好的实践吗?

345

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

我倾向于使用String.format,但偶尔会用拼接。我想知道哪一个更好。

我认为,String.format在“格式化”字符串方面给您更多的控制权;而拼接意味着您不必担心意外地输入额外的%s或漏掉一个。

String.format也更短。

哪个更易读取取决于您的思维方式。


我认为我们可以使用MessageFormat.format。更多信息请参见答案https://dev59.com/CnNA5IYBdhLWcg3whuZ_#56377112。 - Ganesa Vijayakumar
16个回答

281

我建议更好的做法是使用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)

2
请问你能帮我找一些关于在Java中如何处理参数位置/顺序的文档吗?也就是说,如何通过它们的位置引用参数?谢谢。 - markvgti
16
迟做总比不做好,随机的Java版本:http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Formatter.html#syntax - Aksel

241

关于性能:

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快得多。


21
都是不好的做法。使用 StringBuilder。 - Amir Raminfar
16
StringBuilder在这里超出了作用域(原帖问题是关于比较String.format和字符串连接的),但你是否有关于StringBuilder的性能数据? - Icaro
148
编译器会自动将“+”转换为对StringBuilder的调用。 - Martin Schröder
53
如果运行javap -c StringTest.class,你会发现编译器会自动将“+”转换为StringBuilder,但仅限于不在循环中的情况。如果连接操作在单行完成,则与使用“+”相同,但如果你在多行上使用myString += "morechars";myString += anotherString;,你会注意到可能创建了超过一个StringBuilder,因此使用“+”并不总是比StringBuilder更高效。 - ccpizza
7
我理解您的意思是,在for循环中,+符号不会被转换为StringBuilder.append(),而是在每次迭代时都会发生new StringBuilder() - ccpizza
显示剩余3条评论

61
< p >使用.format的一个问题是你会失去静态类型安全。你可能会为格式化提供太少的参数,或者提供错误类型的格式化说明符 - 这两种情况都会导致在运行时出现IllegalFormatException,因此你可能最终会发现日志记录代码破坏了生产环境。

相比之下,使用+的参数可以由编译器进行测试。

printfformat函数的原型)的很长且令人担忧。


28
值得一提的是,现代集成开发环境(例如IntelliJ)可以协助匹配参数数量和类型。 - Ron Klein
3
编译方面的观点很好,我建议您通过FindBugs进行这些检查(可以在IDE或Maven构建期间运行),请注意,这也将检查所有日志记录的格式!这适用于用户IDE。 - Christophe Roussy

47

鉴于关于性能的讨论,我觉得加入一个包括 StringBuilder 的比较会更好。实际上,StringBuilder 比 concat 和 String.format 更快。

为了进行一种类似苹果与苹果之间的比较,我在循环中创建了一个新的 StringBuilder 而不是在外部创建(这实际上比只创建一个 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 INFO [TestMain] - 格式化 = 1416 毫秒
  • 2012-01-11 16:30:46,190 INFO [TestMain] - 字符串连接 = 134 毫秒
  • 2012-01-11 16:30:46,313 INFO [TestMain] - StringBuilder = 117 毫秒

28
StringBuilder 测试没有调用 toString(),因此这并不是一个公平的比较。如果你修复了这个 bug,我怀疑你会发现它的性能在测量误差范围之内,与拼接字符串的性能相当。 - Jamey Sharp
18
在连接和格式测试中,您需要一个“字符串”。为了公平起见,StringBuilder测试需要一个最后一步,将StringBuilder的内容转换为String。通过调用bldString.toString()来实现。希望这能解释清楚? - Jamey Sharp
4
Jamey Sharp说得非常正确。调用bldString.toString()与字符串连接相比速度几乎相同,如果不是更慢的话。 - Akos Cz
3
通过拼接和字符串构建器的时间几乎相当:格式化 = 1520 毫秒拼接 = 167 毫秒字符串构建器 = 173 毫秒我在一个循环中运行它们,并对每个进行平均以获得良好的重复性:(在 jvm 优化之前运行,将在有时间时尝试 10000+ 循环) 代码中的 String s = bldString.toString(); 需要翻译。 - TechTrip
8
你们怎么确定代码是否被执行了?这些变量从未被读取或使用,你无法确定JIT是否在首次将此代码移除。 - alobodzk
显示剩余6条评论

24

哪个更易读取,这取决于你的思维方式。

你已经得到了答案。

这是个人口味的问题。

字符串连接稍微快一点,但应该可以忽略不计。


4
我同意。在这里考虑性能差异主要只是过早的优化——如果分析显示这里存在问题,那么再担心它就好了。 - Jonik
3
如果项目规模较小且无意进行有意义的国际化,那么这只是个人口味问题。否则,String.format在各个方面都胜过字符串连接方式。 - workmad3
9
我不同意。无论项目有多大,你几乎不可能本地化其中构建的每个字符串。换句话说,这取决于情况(字符串用于什么)。 - Jonik
1
我无法想象有人会认为'String.format("%s%s", a, b)'比'a+b'更易读,在不需要本地化的情况下(例如调试或大多数日志记录语句),考虑到速度上数量级的差异,这个答案对我来说似乎很明显。 - BobDoolittle

20

这是一个包含不同样本大小的以毫秒为单位的测试。

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

2
StringBuilder是在循环中添加字符时绝对最快的方法,例如,当您想通过逐个添加它们来创建一个包含一千个1的字符串时。这里有更多信息:http://www.pellegrino.link/2015/08/22/string-concatenation-with-java-8.html - Carlos Hoyos
我喜欢你总是使用String.format进行输出:D,这样有一个优点。老实说,如果我们不谈论数百万次迭代,我更喜欢使用string.format来提高可读性,因为你的代码显示了明显的优势! - mohamnag

11

这里与上文相同的测试,唯一的修改是在 StringBuilder 上调用了 toString() 方法。下面的结果表明,使用 + 操作符进行字符串连接的速度稍微快于 StringBuilder 方法。

文件: 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

9

String.format()不仅仅是字符串拼接。例如,您可以使用String.format()在特定语言环境下显示数字。

但是,如果您不关心本地化,那么没有功能上的区别。 也许拼接比其他方法更快,但在大多数情况下,这将是微不足道的。


6
通常情况下,应该优先选择字符串连接而不是 String.format。后者有两个主要缺点:
  1. 它不能以本地方式编码要构建的字符串。
  2. 构建过程被编码在一个字符串中。
关于第一点,我的意思是在单个顺序通行中无法理解 String.format() 调用正在执行什么操作。必须在格式化字符串和参数之间来回切换,同时计算参数的位置。对于短连接,这并不是什么问题。然而,在这些情况下,字符串连接更简洁。
关于第二点,我的意思是构建过程的重要部分被编码在 格式化字符串 中(使用DSL)。使用字符串表示代码有许多缺点。它不是固有的类型安全,并且使语法高亮,代码分析,优化等变得复杂。
当然,在使用Java语言外部的工具或框架时,可能会出现新的因素。

3

错误测试重复多次 您应该使用{}而不是%s。

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("Wrong use of the message format  = " + ((end - start)) + " millisecond");

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

}

Concatenation = 88 millisecond
Wrong use of the message format  = 1075 millisecond 
Good use of the message format = 376 millisecond

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接