我们必须一直为日志输出构建字符串等等。在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。 我有的是时间,有的是生活。

其他回答

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

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

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

实际上不需要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”块

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

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

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

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

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

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

我使用了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。 我有的是时间,有的是生活。

这个问题的答案在很大程度上取决于您特定的Java编译器如何优化它生成的字节码。字符串是不可变的,理论上,每个“+”操作都可以创建一个新的字符串。但是,你的编译器几乎肯定会优化掉构建长字符串的中间步骤。上面的两行代码完全有可能生成完全相同的字节码。

唯一真正了解的方法是在当前环境中迭代地测试代码。编写一个QD应用程序,以迭代的方式连接字符串,并查看它们如何彼此超时。