我们必须一直为日志输出构建字符串等等。在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入口。它包括一个字符串构建器选项。

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之后的时间

其他回答

我只是修改了hhafez的测试,以包括StringBuilder。StringBuilder比String快33倍。格式使用jdk 1.6.0_10客户端XP。使用-server开关将该因子降低到20。

public class StringTest {

   public static void main( String[] args ) {
      test();
      test();
   }

   private static void test() {
      int i = 0;
      long prev_time = System.currentTimeMillis();
      long time;

      for ( i = 0; i < 1000000; 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 < 1000000; i++ ) {
         String s = String.format("Blah %d Blah", i);
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         new StringBuilder("Blah").append(i).append("Blah");
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);
   }
}

虽然这听起来可能很激烈,但我认为它只在极少数情况下相关,因为绝对数字非常低:100万个简单字符串对应4个s。格式调用是可以的-只要我使用他们的日志或类似的。

更新:正如sjbotha在评论中指出的,StringBuilder测试是无效的,因为它缺少最后的. tostring()。

在我的机器上,从String.format(.)到StringBuilder的正确加速因子是23(使用-server开关时为16)。

仅从日志的角度看另一个角度。

我在这个帖子上看到了很多关于登录的讨论,所以我想在回答中加入我的经验。也许有人会觉得有用。

我猜使用格式化程序进行日志记录的动机来自于避免字符串连接。基本上,如果你不打算记录它,你不希望有字符串连接的开销。

实际上不需要concat/format,除非您想记录日志。假设我这样定义一个方法

public void logDebug(String... args, Throwable t) {
    if(debugOn) {
       // call concat methods for all args
       //log the final debug message
    }
}

在这种方法中,如果它是一个调试消息并且debugOn = false,则根本不会真正调用canat /formatter

尽管在这里使用StringBuilder而不是formatter会更好。我们的主要动机就是避免这些。

同时,我不喜欢为每个日志语句添加“if”块

它会影响可读性 减少单元测试的覆盖率——当你想要确保每一行都经过测试时,这会让人感到困惑。

因此,我更喜欢用上面的方法创建一个日志实用程序类,并在任何地方使用它,而不用担心性能损失和其他与之相关的问题。

下面是修改后的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之后的时间

考虑使用“hello”。Concat(“世界!”)对于连接的少量字符串。它甚至比其他方法的性能更好。

如果你有超过3个字符串,考虑使用StringBuilder,或者只使用String,这取决于你使用的编译器。

你的旧样式会被JAVAC 1.6自动编译为:

StringBuilder sb = new StringBuilder("What do you get if you multiply ");
sb.append(varSix);
sb.append(" by ");
sb.append(varNine);
sb.append("?");
String s =  sb.toString();

所以这和使用StringBuilder是没有区别的。

字符串。format要重量级得多,因为它创建了一个新的Formatter,解析输入格式字符串,创建一个StringBuilder,将所有内容附加到它并调用toString()。