我们必须一直为日志输出构建字符串等等.在JDK版本中,我们学会了何时使用StringBuffer
(许多追加,线程安全)和StringBuilder
(许多追加,非线程安全).
有什么建议使用String.format()
?它是否有效,或者我们是否被迫坚持连接性能很重要的单线?
例如丑陋的旧式,
String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";
与整洁的新风格(可能很慢),
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次):
接近时间(ms)分配的内存(长)
'+'运算符747 320,504
String.format 16484 373,312
StringBuilder 769 57,344
我们可以看到String'+'和StringBuilder在时间上几乎完全相同,但StringBuilder在内存使用方面更有效.当我们在足够短的时间间隔内有许多日志调用(或任何其他涉及字符串的语句)时,这非常重要,因此垃圾收集器无法清除由"+"运算符产生的许多字符串实例.
并且注意,BTW,不要忘记在构造消息之前检查日志记录级别.
结论:
我将继续使用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.format
慢了5-30倍.
原因是在当前实现中String.format
首先使用正则表达式解析输入,然后填充参数.另一方面,与plus的串联由javac(而不是JIT)优化并StringBuilder.append
直接使用.
这里提出的所有基准都有一些缺陷,因此结果不可靠.
我很惊讶没有人使用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虚拟机.
所以,旧式(使用+)要快得多.
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完全没有区别.
String.format是更重量级的,因为它创建了一个新的Formatter,解析了你的输入格式字符串,创建了一个StringBuilder,将所有内容附加到它并调用toString().
Java的String.format的工作原理如下:
它解析格式字符串,爆炸成格式块列表
它迭代格式块,渲染成StringBuilder,它基本上是一个通过复制到新数组来根据需要调整自身大小的数组.这是必要的,因为我们还不知道分配最终String的大小
StringBuilder.toString()将其内部缓冲区复制到新的String中
如果此数据的最终目的地是流(例如,呈现网页或写入文件),则可以将格式块直接组合到流中:
new PrintStream(outputStream, autoFlush, encoding).format("hello {0}", "world");
我推测优化器会优化格式字符串处理.如果是这样,您将获得等效的摊销性能,以手动将String.format展开到StringBuilder中.
为了扩展/纠正上面的第一个答案,实际上并不是String.format会提供帮助的翻译.
String.format将帮助您的是当您打印日期/时间(或数字格式等),其中存在本地化(l10n)差异(即,一些国家将打印04Feb2009而其他国家将打印Feb042009).
通过翻译,您只是谈论将任何可外部化的字符串(如错误消息和什么不是)移动到属性包中,以便您可以使用ResourceBundle和MessageFormat将正确的语言包用于正确的语言.
综上所述,我会说性能方面,String.format与简单连接归结为你喜欢的.如果您更喜欢查看.format对连接的调用,那么请务必使用它.
毕竟,代码的读取次数比编写的要多得多.
在您的示例中,性能probalby并没有太大的不同,但还有其他需要考虑的问题:即内存碎片.甚至连接操作也会创建一个新字符串,即使它是临时的(GC需要时间,而且工作更多).String.format()只是更具可读性,它涉及更少的碎片.
此外,如果您经常使用特定格式,请不要忘记您可以直接使用Formatter()类(所有String.format()都会实例化一个使用Formatter实例).
另外,您应该注意的其他事项:小心使用substring().例如:
String getSmallString() { String largeString = // load from file; say 2M in size return largeString.substring(100, 300); }
那个大字符串仍在内存中,因为这只是Java子字符串的工作方式.更好的版本是:
return new String(largeString.substring(100, 300));
要么
return String.format("%s", largeString.substring(100, 300));
如果你同时做其他的事情,第二种形式可能更有用.
通常你应该使用String.Format,因为它相对较快并且它支持全球化(假设你实际上是在尝试编写用户读取的东西).如果您尝试翻译一个字符串而不是每个语句3个或更多(特别是对于具有截然不同的语法结构的语言),它还可以更容易全球化.
现在,如果您从未计划翻译任何内容,那么要么依赖Java内置的+运算符转换StringBuilder
.或者StringBuilder
明确地使用Java .