当前位置:  开发笔记 > 编程语言 > 正文

如果性能很重要,我应该使用Java的String.format()吗?

如何解决《如果性能很重要,我应该使用Java的String.format()吗?》经验,为你挑选了8个好方法。

我们必须一直为日志输出构建字符串等等.在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()特别感兴趣.



1> 小智..:

我拿了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.

    我有太多的时间或太少的生命.


不,这不对.不好意思是直言不讳,但它吸引的赞成数量令人震惊.使用`+`运算符编译为等效的`StringBuilder`代码.像这样的微型计算机不是测量性能的好方法 - 为什么不使用jvisualvm,它在jdk中是有原因的.`String.format()`*会慢*,但由于时间解析格式字符串而不是任何对象分配.推迟创建日志工件,直到你确定它们是需要的*是*好建议,但如果它会对性能产生影响,那就是错误的地方.
"在构造消息之前不要忘记检查日志记录级别",这是一个很好的建议,至少应该对调试消息进行此操作,因为可能存在很多这些消息,并且它们不应该在生产中启用.

2> hhafez..:

我写了一个小类来测试哪个具有更好的性能,并且+领先于格式.尝试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直接使用.

运行时比较


这看起来很像那种将以非常无用的方式进行优化的微基准测试.
另一个执行不力的微基准测试.两种方法如何按数量级进行扩展.如何使用,100,1000,10000,1000000,操作.如果您只在一个未隔离的核心上运行的应用程序上运行一个数量级的测试; 由于上下文切换,后台进程等原因,没有办法说明有多少差异可以作为"副作用"被注销.
这个测试存在一个缺陷,它并不完全是所有字符串格式的良好表示.通常有什么逻辑涉及将特定值格式化为字符串的内容和逻辑.任何真正的测试都应该关注真实场景.
还有另外一个关于+经文StringBuffer的问题,在最近的Java +版本中尽可能用StringBuffer替换,所以性能不会有所不同
此外,因为你没有从主JIT出来不能踢.
@EvanPlaice OP没有在一行中使用100,1000,10000或1000000操作,因此这与他的问题不直接相关.
只是想添加String.format在内部也使用StringBuilder。(至少在openjdk8中)。因此,基本上是区域设置/正则表达式/格式开销+ StringBuilder与仅StringBuilder。即使基准执行不佳。我认为可以肯定地说,仅执行“ x”比执行“ y” +“ x”要快

3> Adam Stelmas..:

这里提出的所有基准都有一些缺陷,因此结果不可靠.

我很惊讶没有人使用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虚拟机.

所以,旧式(使用+)要快得多.


如果你注释哪个是"+"并且是"格式",这将更容易解释.

4> Raphaël..:

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().


`+`和`StringBuilder`之间没有区别.不幸的是,在这个帖子中,其他答案中有很多错误的信息.我几乎想把这个问题改成"我怎么不测量性能".

5> Dustin Getz..:

Java的String.format的工作原理如下:

    它解析格式字符串,爆炸成格式块列表

    它迭代格式块,渲染成StringBuilder,它基本上是一个通过复制到新数组来根据需要调整自身大小的数组.这是必要的,因为我们还不知道分配最终String的大小

    StringBuilder.toString()将其内部缓冲区复制到新的String中

如果此数据的最终目的地是流(例如,呈现网页或写入文件),则可以将格式块直接组合到流中:

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

我推测优化器会优化格式字符串处理.如果是这样,您将获得等效的摊销性能,以手动将String.format展开到StringBuilder中.


我不认为你对优化格式字符串处理的猜测是正确的.在使用Java 7的一些实际测试中,我发现在内部循环中使用`String.format`(运行数百万次)导致我在`java.util.Formatter.parse(String)中花费的执行时间的10%以上)`.这似乎表明在内部循环中,你应该避免调用`Formatter.format`或任何调用它的东西,包括`PrintStream.format`(Java的标准库,IMO中的一个缺陷,特别是因为你无法缓存解析后的格式串).

6> dw.mackie..:

为了扩展/纠正上面的第一个答案,实际上并不是String.format会提供帮助的翻译.
String.format将帮助您的是当您打印日期/时间(或数字格式等),其中存在本地化(l10n)差异(即,一些国家将打印04Feb2009而其他国家将打印Feb042009).
通过翻译,您只是谈论将任何可外部化的字符串(如错误消息和什么不是)移动到属性包中,以便您可以使用ResourceBundle和MessageFormat将正确的语言包用于正确的语言.

综上所述,我会说性能方面,String.format与简单连接归结为你喜欢的.如果您更喜欢查看.format对连接的调用,那么请务必使用它.
毕竟,代码的读取次数比编写的要多得多.



7> cletus..:

在您的示例中,性能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));

如果你同时做其他的事情,第二种形式可能更有用.


值得指出的是"相关问题"实际上是C#,因此不适用.

8> Orion Adrian..:

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

现在,如果您从未计划翻译任何内容,那么要么依赖Java内置的+运算符转换StringBuilder.或者StringBuilder明确地使用Java .

推荐阅读
落单鸟人
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有