字符串拼接的效率低下问题

9

我是一名Java程序员,使用NetBeans作为开发工具,在我的应用程序中创建了一个日志记录器。突然间,我看到了一个警告: “Logger中字符串拼接的效率较低。”

我的原始代码如下:

srcLogger.getLogger().log(Level.INFO,"UploadBean.doUpload completado [" + file.getName() + "]\n");

但NetBeans建议将其转换为模板(这里的“模板”是什么意思?)并提供以下代码:
srcLogger.getLogger().log(Level.INFO, "UploadBean.doUpload completado [{0}]\n", file.getName());

这两种连接方式有什么不同呢?尽管我从未使用过后者。 乾杯!
6个回答

18

这条消息并没有涉及到字符串拼接本身的成本。当其他答案提到使用 StringBuilder 时,它们是绝对正确的。

使用 Message Template 的主要原因是处理仅在显示日志级别时进行!

让我们来看这两个例子:

srcLogger.getLogger().log(Level.INFO,"UploadBean.doUpload completado [" + file.getName() + "]\n");
srcLogger.getLogger().log(Level.INFO, "UploadBean.doUpload completado [{0}]\n", file.getName());

使用调试级别为INFO: 两者都需要从文件中获取文件名,都需要更新字符串,生成一个新的字符串并显示它。

使用调试级别为INFO OFF: 第二个答案通过文件对象的名称(一个简单的查询)进行传递,log() 方法检查INFO级别并立即返回,没有执行任何String处理!

现在想象一下,如果我们记录的是一个更复杂的对象,一个在其toString()方法中需要进行大量字符串拼接的对象。通过直接记录这些对象,就不会执行任何处理了。除非正在显示调试级别,否则根本不会调用toString()

因此,当日志未被显示时,消息模板并不比其他方式更高效,但在日志不被显示时(特别是在非平凡日志情况下),它要高效得多。日志的目标之一应该是,如果关闭日志记录,则对系统性能的影响尽可能小。


12

我建议忽略这个警告(如果可能的话,关闭它)。因为现代编译器会使用基于 StringBuilder 的高效实现来替换字符串拼接,所以它并不会那么低效(如果你查看类文件的字节码,你会看到这一点)。

虽然建议的替代方法不是通过字符串拼接来连接字符串,但它需要一些额外的处理来解析模板并将其与参数合并。

Netbeans 给出的建议很糟糕。

对于 Java 1.5+ 版本来说这是正确的。旧版本的 Java 可能会在字符串拼接过程中创建大量未使用的 String 实例。


3
要关闭警告(在NetBeans 7.2.1中):首选项->编辑器->提示->日志记录->日志记录中的字符串连接。描述说:“在记录器消息中连接字符串不是性能高效的做法。最好使用一个包含占位符的模板消息,当消息真正要被记录时,才使用具体值替换占位符。” - Philip Durbin
实际上,您错过了其中一个主要优点,尽管现在有些晚了,我会添加一个答案 :) - Tim B
3
然而,警告存在一种低效率,因为连接操作总是被执行,而不是仅在使用 log(Level level, String msg, Object[] params) 方法时根据当前日志级别决定是否执行。 - Adam
哦,不同意,这绝对是很好的建议。日志框架会尽力避免不必要的.toString()和字符串连接操作;自己构造字符串确保即使未使用,也必须完成这项工作。考虑一个打印列表的fine()日志。构造字符串版本的列表需要O(n)的时间,但最终在生产环境中该字符串将被丢弃。让日志记录器处理字符串构造能够使它尽可能少做工作。 - dimo414

11

真正的优势在于,如果记录器没有配置为以INFO级别记录日志,那么你根本不需要进行任何字符串处理(包括字符串连接或模板扩展)。

也就是说,记录器可以决定什么都不做,而无需接近任何类型的字符串操作。


4

在Java中,由于字符串是不可变的,当你连接String对象时,实际上会创建一个全新的对象。使用类似Netbeans建议的模板或者使用StringBuilder可以避免创建那些中间对象,这需要时间和资源。


除了现代的Java编译器会为您使用StringBuilder之外,在这种情况下并不是这个原因。 - Tim B

1
一个模板的意思就是它只是一个字符串的模板,而不是一个完整的字符串。这个想法是,{0} 这一部分将会被列表中紧随其后的第一个参数(file.getName())所替换。这遵循了 Stringformat 方法的模式。

我没有看到任何性能测试来验证这是否更快。正如其他答案所指出的那样,保持原样不会特别慢,因为编译器将使用StringBuilder代替常规字符串。然而,正如@dty所指出的那样,如果日志级别设置为实际上不记录该语句,则应该更快,因为不需要构造要输出的字符串。此外,由于整个模板字符串是单个文字,因此编译器将其添加到字符串池中。这意味着该特定String的所有实例都将指向同一个实际实例-因此,如果实际上未记录该语句,则甚至不必分配内存来存储此字符串,只需查找它,这应该更有效率。


1

NetBeans 给出的警告为避免在日志语句中使用连接操作符提供了最短的理由。

  1. 当您使用模板样式时,不会构造不会被发送到日志的日志消息。您甚至可以通过避免在参数列表中调用方法来进一步优化样式。

但是,选择使用模板样式记录日志消息还有其他原因。

a. 避免可能的连接开销。正如其他人指出的那样,这对于最近的 javac 来说并不是一个很大的问题。

b. 您的代码更好地准备好进行国际化/本地化。虽然您可能认为...这段代码永远不需要那种程度的关注...但令人惊讶的是,代码在最初编写后走得有多远。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接