使用String是不是更好的实践?在Java串接格式?

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

我倾向于使用String.format,但偶尔会滑倒并使用连接。我想知道哪个比哪个好。

在我看来,String.format在“格式化”字符串方面给了你更多的力量;连接意味着您不必担心不小心输入了额外的%s或遗漏了一个。

String.format也更短。

哪一个更容易读,取决于你的大脑如何工作。

229569 次浏览

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

哪一个更容易读,取决于你的大脑如何工作。

你已经得到答案了。

这是个人喜好的问题。

我认为,字符串连接稍微快一些,但这应该可以忽略不计。

String.format()不仅仅是串接字符串。例如,可以使用String.format()显示特定地区的数字。

然而,如果你不关心本地化,就没有功能上的区别。 也许这个连接比另一个快,但在大多数情况下,它可以忽略不计

我没有做任何具体的基准测试,但我认为连接可能更快。String.format()创建一个新的Formatter,该Formatter反过来创建一个新的StringBuilder(大小只有16个字符)。这是一个相当大的开销,特别是当您正在格式化一个较长的字符串并且StringBuilder必须不断调整大小时。

然而,串联不太有用,也更难阅读。一如既往,有必要对代码进行基准测试,看看哪个更好。在服务器应用程序中,当你的资源包、locale等加载到内存中,代码被jit后,这些差异可以忽略不计。

也许作为一个最佳实践,它将是一个好主意,创建自己的Formatter与适当大小的StringBuilder(可追加的)和地区,并使用它,如果你有很多格式化要做。

性能:

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

你不能比较字符串连接和字符串。格式由上面的程序。

你也可以尝试交换使用你的字符串的位置。格式和连接在你的代码块如下所示

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在这里工作得更快。这是因为创建的初始对象可能不会被释放,内存分配可能会出现问题,从而影响性能。

可能会有明显的差别。

String.format非常复杂,在底层使用正则表达式,所以不要习惯在任何地方使用它,只在需要它的地方使用。

StringBuilder会快一个数量级(正如这里有人已经指出的那样)。

由于有关于性能的讨论,我想我应该添加一个包含StringBuilder的比较。事实上,它比concat更快,当然也比String更快。格式选项。

为了使这成为一种苹果对苹果的比较,我在循环中而不是在外部实例化了一个新的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毫秒
  • 2012-01-11 16:30:46,190 INFO [TestMain] -连接= 134毫秒
  • 2012-01-11 16:30:46,313 INFO [TestMain] - String Builder = 117毫秒

.format的一个问题是你失去了静态类型安全性。格式的参数可能太少,格式说明符的类型也可能错误——这两者都会导致IllegalFormatException 在运行时,因此可能会导致日志代码中断生产。

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

安全的历史 (format函数是基于它建模的)很长,很可怕。

下面是与上面相同的测试,只是修改了在StringBuilder上调用< em > toString () < / em >方法。下面的结果表明,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 Commands:(编译并运行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

这是一个以毫秒为单位的多个样本大小的测试。

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

通常,字符串连接应该优先于String.format。后者有两个主要缺点:

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

第一点,我的意思是不可能理解String.format()调用在单个连续传递中所做的事情。人们被迫在格式字符串和参数之间来回切换,同时计算参数的位置。对于短连接,这不是什么大问题。但是,在这些情况下,字符串连接不那么详细。

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

当然,在使用Java语言之外的工具或框架时,可能会出现新的因素。

它需要一点时间来适应字符串。格式,但在大多数情况下是值得的。在NRA(从不重复任何事情)的世界中,将标记化的消息(日志或用户)保存在Constant库中(我更喜欢相当于静态类的内容)并在必要时使用String调用它们是非常有用的。格式,不管你是否本地化。尝试使用连接方法来使用这样的库,使用任何需要连接的方法来阅读、排除故障、校对和管理都比较困难。更换是一个选择,但我怀疑它的性能。经过多年的使用,我最大的问题与String。格式是当我将它传递给另一个函数(如Msg)时,调用的长度很长,但这很容易通过自定义函数作为别名来解决。

我认为我们可以使用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格式字符串是在运行时解释的,而不是由编译器验证,所以它们可能包含导致创建错误字符串的错误。当调用java.util.Formatterjava.lang.Stringjava.io.PrintStreamMessageFormatjava.io.PrintWriter类的format(…)方法和java.io.PrintStreamjava.io.PrintWriter类的printf(...)方法时,此规则静态验证printf-style格式字符串与其参数的相关性。

我用括号替换了printf样式,得到了一些有趣的结果,如下所示。

串联= 69毫秒
格式= 1107毫秒
格式:括号= 416毫秒
MessageFormat = 215 MessageFormat:括号= 2517毫秒

< p > 我的结论是: < br > 如上所述,使用String。带尖括号的格式应该是一个很好的选择,以获得良好的可读性和性能

重复多次错误测试

.{}不能使用%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