我们必须一直为日志输出构建字符串等等。在JDK版本中,我们已经学习了什么时候使用StringBuffer(许多追加,线程安全)和StringBuilder(许多追加,非线程安全)。

使用String.format()有什么建议?它是有效的,还是我们被迫坚持在性能很重要的一行程序中使用连接?

例如,丑陋的老式风格,

String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";

vs.整洁的新样式(字符串。格式,可能更慢),

String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);

注意:我的特定用例是代码中的数百个“一行”日志字符串。它们不涉及循环,所以StringBuilder太重量级了。我对String.format()特别感兴趣。


当前回答

我使用了hhafez的代码并添加了一个内存测试:

private static void test() {
    Runtime runtime = Runtime.getRuntime();
    long memory;
    ...
    memory = runtime.freeMemory();
    // for loop code
    memory = memory-runtime.freeMemory();

我为每一种方法分别运行这个程序,'+'操作符,String。format和StringBuilder(调用toString()),因此所使用的内存不会受到其他方法的影响。 我添加了更多的连接,使字符串为“Blah”+ I +“Blah”+ I +“Blah”+ I +“Blah”。

结果如下(平均每次5次):

Approach Time(ms) Memory allocated (long)
+ operator 747 320,504
String.format 16484 373,312
StringBuilder 769 57,344

我们可以看到String +和StringBuilder在时间上实际上是相同的,但是StringBuilder在内存使用上要高效得多。 当我们在足够短的时间间隔内有许多日志调用(或任何其他涉及字符串的语句)时,这是非常重要的,因此垃圾收集器将无法清理+操作符导致的许多字符串实例。

顺便说一句,在构造消息之前,不要忘记检查日志级别。

结论:

我将继续使用StringBuilder。 我有的是时间,有的是生活。

其他回答

我写了一个小类来测试两者中哪个具有更好的性能,并且+优先于格式。以5到6的倍数。 你自己试试吧

import java.io.*;
import java.util.Date;

public class StringTest{

    public static void main( String[] args ){
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;

    for( i = 0; i< 100000; i++){
        String s = "Blah" + i + "Blah";
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<100000; i++){
        String s = String.format("Blah %d Blah", i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    }
}

对不同的N运行上面的代码,可以看出两者都是线性的,但是String。格式要慢5-30倍。

原因是在当前的String实现中。Format首先用正则表达式解析输入,然后填充参数。另一方面,使用plus的连接由javac(而不是JIT)优化,并使用StringBuilder。直接添加。

通常应该使用String。格式,因为它相对较快,并且支持全球化(假设您实际上正在尝试编写用户可以阅读的内容)。如果您试图翻译一个字符串,而不是每个语句翻译3个或更多字符串(特别是对于语法结构截然不同的语言),它还可以使全球化变得更容易。

现在,如果您不打算翻译任何东西,那么要么依赖于Java内置的+运算符到StringBuilder的转换。或者显式地使用Java的StringBuilder。

下面是修改后的hhafez入口。它包括一个字符串构建器选项。

public class BLA
{
public static final String BLAH = "Blah ";
public static final String BLAH2 = " Blah";
public static final String BLAH3 = "Blah %d Blah";


public static void main(String[] args) {
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;
    int numLoops = 1000000;

    for( i = 0; i< numLoops; i++){
        String s = BLAH + i + BLAH2;
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<numLoops; i++){
        String s = String.format(BLAH3, i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<numLoops; i++){
        StringBuilder sb = new StringBuilder();
        sb.append(BLAH);
        sb.append(i);
        sb.append(BLAH2);
        String s = sb.toString();
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

}

}

循环391之后的时间 循环4163之后的时间 循环227之后的时间

Java的字符串。格式如下:

它解析格式字符串,分解成一个格式块列表 它迭代格式块,呈现为一个StringBuilder,这基本上是一个数组,根据需要调整自己的大小,通过复制到一个新的数组。这是必要的,因为我们还不知道分配最终字符串的大小 StringBuilder.toString()将他的内部缓冲区复制到一个新的String中

如果这个数据的最终目的地是一个流(例如,渲染一个网页或写入一个文件),你可以将格式块直接组装到你的流中:

new PrintStream(outputStream, autoFlush, encoding).format("hello {0}", "world");

我推测优化器将优化掉格式字符串处理。如果是这样,就剩下了与手动展开String相同的平摊性能。格式转换为StringBuilder。

这里提供的所有基准测试都有一些缺陷,因此结果不可靠。

我很惊讶没有人使用JMH进行基准测试,所以我就这样做了。

结果:

Benchmark             Mode  Cnt     Score     Error  Units
MyBenchmark.testOld  thrpt   20  9645.834 ± 238.165  ops/s  // using +
MyBenchmark.testNew  thrpt   20   429.898 ±  10.551  ops/s  // using String.format

单位是每秒操作次数,越多越好。基准测试源代码。使用OpenJDK IcedTea 2.5.4 Java虚拟机。

所以,旧的样式(使用+)要快得多。