简体   繁体   English

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

[英]Is it better practice to use String.format over string Concatenation in Java?

Is there a perceptible difference between using String.format and String concatenation in Java?在 Java 中使用String.format和字符串连接之间有明显的区别吗?

I tend to use String.format but occasionally will slip and use a concatenation.我倾向于使用String.format但偶尔会滑倒并使用连接。 I was wondering if one was better than the other.我想知道一个是否比另一个更好。

The way I see it, String.format gives you more power in "formatting" the string;在我看来, String.format为您提供了更多“格式化”字符串的能力; and concatenation means you don't have to worry about accidentally putting in an extra %s or missing one out.连接意味着您不必担心不小心放入额外的 %s 或遗漏一个。

String.format is also shorter. String.format也更短。

Which one is more readable depends on how your head works.哪一个更具可读性取决于你的头脑是如何工作的。

I'd suggest that it is better practice to use String.format() .我建议最好使用String.format() The main reason is that String.format() can be more easily localised with text loaded from resource files whereas concatenation can't be localised without producing a new executable with different code for each language.主要原因是String.format()可以更轻松地使用从资源文件加载的文本进行本地化,而如果不为每种语言生成具有不同代码的新可执行文件,则无法对连接进行本地化。

If you plan on your app being localisable you should also get into the habit of specifying argument positions for your format tokens as well:如果您计划将您的应用程序本地化,您还应该养成为格式标记指定参数位置的习惯:

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

This can then be localised and have the name and time tokens swapped without requiring a recompile of the executable to account for the different ordering.然后可以将其本地化并交换名称和时间标记,而无需重新编译可执行文件以考虑不同的排序。 With argument positions you can also re-use the same argument without passing it into the function twice:使用参数位置,您还可以重复使用相同的参数,而无需将其传递给函数两次:

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

About performance:关于性能:

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");
}

The timing results are as follows:计时结果如下:

  • Concatenation = 265 millisecond串联 = 265 毫秒
  • Format = 4141 millisecond格式 = 4141 毫秒

Therefore, concatenation is much faster than String.format.因此,连接比 String.format 快得多。

One problem with .format is that you lose static type safety. .format一个问题是您失去了静态类型安全性。 You can have too few arguments for your format, and you can have the wrong types for the format specifiers - both leading to an IllegalFormatException at runtime , so you might end up with logging code that breaks production.您的格式参数可能太少,格式说明符的类型可能错误 - 两者IllegalFormatException在运行时导致IllegalFormatException ,因此您最终可能会记录中断生产的代码。

In contrast, the arguments to + can be tested by the compiler.相反, +的参数可以由编译器测试。

The security history of (on which the format function is modeled) is long and frightening. format函数的模型)的安全历史漫长而可怕。

Since there is discussion about performance I figured I'd add in a comparison that included StringBuilder.由于有关于性能的讨论,我想我会添加一个包含 StringBuilder 的比较。 It is in fact faster than the concat and, naturally the String.format option.事实上,它比 concat 更快,当然也比 String.format 选项更快。

To make this a sort of apples to apples comparison I instantiate a new StringBuilder in the loop rather than outside (this is actually faster than doing just one instantiation most likely due to the overhead of re-allocating space for the looping append at the end of one builder).为了使这成为一种苹果与苹果的比较,我在循环中而不是在循环中实例化了一个新的 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] - Format = 1416 millisecond 2012-01-11 16:30:46,058 信息 [TestMain] - 格式 = 1416 毫秒
  • 2012-01-11 16:30:46,190 INFO [TestMain] - Concatenation = 134 millisecond 2012-01-11 16:30:46,190 信息 [TestMain] - 连接 = 134 毫秒
  • 2012-01-11 16:30:46,313 INFO [TestMain] - String Builder = 117 millisecond 2012-01-11 16:30:46,313 信息 [TestMain]-字符串生成器 = 117 毫秒

Which one is more readable depends on how your head works.哪一个更具可读性取决于您的大脑如何工作。

You got your answer right there.你就在那里得到了答案。

It's a matter of personal taste.这是个人口味的问题。

String concatenation is marginally faster, I suppose, but that should be negligible.我想,字符串连接稍微快一点,但这应该可以忽略不计。

Here's a test with multiple sample sizes in milliseconds.这是一个以毫秒为单位的多个样本大小的测试。

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

Here's the same test as above with the modification of calling the toString() method on the StringBuilder .这是与上面相同的测试,修改了在StringBuilder上调用toString()方法。 The results below show that the StringBuilder approach is just a bit slower than String concatenation using the + operator.下面的结果表明 StringBuilder 方法比使用+运算符的字符串连接慢一点。

file: StringTest.java文件: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 Commands : (compile and run StringTest 5 times) Shell 命令:(编译并运行 StringTest 5 次)

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

Results :结果 :

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

Generally, string concatenation should be prefered over String.format .通常,字符串连接应该优先于String.format The latter has two main disadvantages:后者有两个主要缺点:

  1. It does not encode the string to be built in a local manner.它不会对要以本地方式构建的字符串进行编码。
  2. The building process is encoded in a string.构建过程以字符串编码。

By point 1, I mean that it is not possible to understand what a String.format() call is doing in a single sequential pass.第 1 点,我的意思是不可能理解String.format()调用在单个顺序传递中正在做什么。 One is forced to go back and forth between the format string and the arguments, while counting the position of the arguments.一个人被迫在格式字符串和参数之间来回移动,同时计算参数的位置。 For short concatenations, this is not much of an issue.对于短连接,这不是什么大问题。 In these cases however, string concatenation is less verbose.然而,在这些情况下,字符串连接不那么冗长。

By point 2, I mean that the important part of the building process is encoded in the format string (using a DSL).第 2 点,我的意思是构建过程的重要部分是在格式字符串中编码的(使用 DSL)。 Using strings to represent code has many disadvantages.使用字符串来表示代码有很多缺点。 It is not inherently type-safe, and complicates syntax-highlighting, code analysis, optimization, etc.它本质上不是类型安全的,并且使语法突出显示、代码分析、优化等复杂化。

Of course, when using tools or frameworks external to the Java language, new factors can come into play.当然,在使用 Java 语言外部的工具或框架时,新的因素可能会发挥作用。

String.format() is more than just concatenating strings. String.format()不仅仅是连接字符串。 For example, you can display numbers in a specific locale using String.format() .例如,您可以使用String.format()在特定语言环境中显示数字。

However, if you don't care about localisation, there is no functional difference.但是,如果您不关心本地化,则没有功能差异。 Maybe the one is faster than the other, but in most cases it will be negligible..也许一个比另一个快,但在大多数情况下,它可以忽略不计。

Wrong test repeated many times You should use {} no %s.错误的测试重复了很多次你应该使用 {} no %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

There could be a perceptible difference.可能会有明显的差异。

String.format is quite complex and uses a regular expression underneath, so don't make it a habit to use it everywhere, but only where you need it. String.format非常复杂,并且在下面使用了正则表达式,所以不要养成在任何地方使用它的习惯,而只在需要的地方使用它。

StringBuilder would be an order of magnitude faster (as someone here already pointed out). StringBuilder会快一个数量级(正如这里有人已经指出的那样)。

I haven't done any specific benchmarks, but I would think that concatenation may be faster.我没有做过任何具体的基准测试,但我认为串联可能会更快。 String.format() creates a new Formatter which, in turn, creates a new StringBuilder (with a size of only 16 chars). String.format() 创建了一个新的 Formatter,它反过来又创建了一个新的 StringBuilder(大小只有 16 个字符)。 That's a fair amount of overhead especially if you are formatting a longer string and StringBuilder keeps having to resize.这是相当大的开销,特别是如果您正在格式化更长的字符串并且 StringBuilder 必须不断调整大小。

However, concatenation is less useful and harder to read.但是,连接的用处不大,而且更难阅读。 As always, it's worth doing a benchmark on your code to see which is better.与往常一样,值得对您的代码进行基准测试以查看哪个更好。 The differences may be negligible in server app after your resource bundles, locales, etc are loaded in memory and the code is JITted.在您的资源包、区域设置等加载到内存中并且代码经过 JIT 处理后,服务器应用程序中的差异可能可以忽略不计。

Maybe as a best practice, it would be a good idea to create your own Formatter with a properly sized StringBuilder (Appendable) and Locale and use that if you have a lot of formatting to do.也许作为最佳实践,使用适当大小的 StringBuilder (Appendable) 和 Locale 创建您自己的 Formatter 是一个好主意,如果您有很多格式要做,请使用它。

I think we can go with MessageFormat.format as it should be good at both readability and also performance aspects.我认为我们可以使用MessageFormat.format因为它应该在可读性和性能方面都很好。

I used the same program which one used by Icaro in his above answer and I enhanced it with appending code for using MessageFormat to explain the performance numbers.我使用了与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");
  }

Concatenation = 69 millisecond串联 = 69 毫秒

Format = 1435 millisecond格式 = 1435 毫秒

MessageFormat = 200 millisecond消息格式 = 200 毫秒

UPDATES:更新:

As per SonarLint Report, Printf-style format strings should be used correctly (squid:S3457)根据 SonarLint 报告,应正确使用 Printf 样式的格式字符串 (squid:S3457)

Because printf-style format strings are interpreted at runtime, rather than validated by the compiler, they can contain errors that result in the wrong strings being created.因为printf-style格式字符串在运行时解释,而不是由编译器验证,所以它们可能包含导致创建错误字符串的错误。 This rule statically validates the correlation of printf-style format strings to their arguments when calling the format(...) methods of java.util.Formatter , java.lang.String , java.io.PrintStream , MessageFormat , and java.io.PrintWriter classes and the printf(...) methods of java.io.PrintStream or java.io.PrintWriter classes.当调用java.util.Formatterjava.lang.Stringjava.io.PrintStreamMessageFormatjava.io.PrintWriter的 format(...) 方法时,此规则静态验证printf-style格式字符串与其参数的相关性java.io.PrintWriter类和java.io.PrintStreamjava.io.PrintWriter类的printf(...)方法。

I replace the printf-style with the curly-brackets and I got something interesting results as below.我用大括号替换了 printf 样式,得到了一些有趣的结果,如下所示。

Concatenation = 69 millisecond串联 = 69 毫秒
Format = 1107 millisecond格式 = 1107 毫秒
Format:curly-brackets = 416 millisecond格式:花括号 = 416 毫秒
MessageFormat = 215 millisecond消息格式 = 215 毫秒
MessageFormat:curly-brackets = 2517 millisecond消息格式:大括号 = 2517 毫秒

My Conclusion:我的结论:
As I highlighted above, using String.format with curly-brackets should be a good choice to get benefits of good readability and also performance.正如我在上面强调的,使用带有花括号的 String.format 应该是一个不错的选择,以获得良好的可读性和性能的好处。

It takes a little time to get used to String.Format, but it's worth it in most cases.习惯 String.Format 需要一点时间,但在大多数情况下是值得的。 In the world of NRA (never repeat anything) it's extremely useful to keep your tokenized messages (logging or user) in a Constant library (I prefer what amounts to a static class) and call them as necessary with String.Format regardless of whether you are localizing or not.在 NRA 的世界中(永远不要重复任何事情),将标记化的消息(日志记录或用户)保存在常量库中(我更喜欢静态类)并在必要时使用 String.Format 调用它们非常有用,无论您是否是否本地化。 Trying to use such a library with a concatenation method is harder to read, troubleshoot, proofread, and manage with any any approach that requires concatenation.尝试使用具有串联方法的此类库更难通过任何需要串联的方法来阅读、排除故障、校对和管理。 Replacement is an option, but I doubt it's performant.更换是一种选择,但我怀疑它的性能。 After years of use, my biggest problem with String.Format is the length of the call is inconveniently long when I'm passing it into another function (like Msg), but that's easy to get around with a custom function to serve as an alias.经过多年的使用,我对 String.Format 的最大问题是,当我将它传递给另一个函数(如 Msg)时,调用的长度很长很不方便,但是使用自定义函数作为别名很容易解决.

You cannot compare String Concatenation and String.Format by the program above.您无法通过上述程序比较 String Concatenation 和 String.Format。

You may try this also be interchanging the position of using your String.Format and Concatenation in your code block like the below您也可以尝试在代码块中交换使用 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") ;
}

You will be surprised to see that Format works faster here.您会惊讶地发现 Format 在这里运行得更快。 This is since the intial objects created might not be released and there can be an issue with memory allocation and thereby the performance.这是因为创建的初始对象可能不会被释放,内存分配可能会出现问题,从而导致性能问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM