【问题标题】:Performance: Java's String.format [duplicate]性能:Java 的 String.format [重复]
【发布时间】:2012-09-29 00:13:28
【问题描述】:

可能重复:
Should I use Java's String.format() if performance is important?

我想知道在 Java 应用程序中使用 String.format 而不是 StringBuilder 是否更好...所以,我只写了一个简单的测试,如下所示:

public static void main(String[] args) {
        int i = 0;
        Long start = System.currentTimeMillis();
        while (i < 10000) {
            String s = String.format("test %d", i);
            i++;
        }
        System.out.println(System.currentTimeMillis() - start);
        i = 0;
        start = System.currentTimeMillis();
        while (i < 10000) {
            String s = new StringBuilder().append("test ").append(i).toString();
            i++;
        }
        System.out.println(System.currentTimeMillis() - start);
    }

结果如下:

238
15

所以,如果我的测试有效,StringBuilderString.format 快。好的。 现在,我开始思考String.format 的工作原理。是不是简单的字符串拼接,比如"test " + i

StringBuilder 串联和String.format 有什么区别?有没有像String.format 这样简单又像StringBuilder 这样快速的方法?

【问题讨论】:

  • 您帖子中的一个重要假设是:“如果我的测试有效” ;-) 您应该阅读how to create a micro benchmark in Java
  • 请记住,第一种情况实际上是String.format("test %d", new Object[]{new Integer(i)}),如果编译器不够聪明,这可能会占开销的很大一部分。
  • String.format 每次都必须解析格式字符串,然后做一些相当动态的事情来转换参数。使用StringBuilder 的方法不要;构建字符串所需的操作序列来自字节码。解析和有效执行格式字符串可能比执行代码要数量级的字符串。
  • 你为什么还要比较这些?尝试使用 StringBuilder 编写类似String.format("% 5.3f") 的内容,然后您就会明白为什么它会变慢。

标签: java performance string-concatenation


【解决方案1】:

我写了一个快速的caliper 基准来比较String.format()StringBuilderStringBuffer、普通String+ 运算符、String.replace()String.concat() 方法:

public class StringFormatBenchmark extends SimpleBenchmark {

    public void timeStringFormat(int reps) {
        while (--reps >= 0) {
            String s = String.format("test %d", reps);
        }
    }

    public void timeStringBuilder(int reps) {
        while (--reps >= 0) {
            String s = new StringBuilder("test ").append(reps).toString();
        }
    }

    public void timeStringBuffer(int reps) {
        while (--reps >= 0) {
            String s = new StringBuffer("test ").append(reps).toString();
        }
    }

    public void timeStringPlusOperator(int reps) {
        while (--reps >= 0) {
            String s = "test " + reps;
        }
    }

    public void timeReplace(int reps) {
        while (--reps >= 0) {
            String s = "test {}".replace("{}", String.valueOf(reps));
        }
    }

    public void timeStringConcat(int reps) {
        while (--reps >= 0) {
            String s = "test ".concat(String.valueOf(reps));
        }
    }

    public static void main(String[] args) {
        new Runner().run(StringFormatBenchmark.class.getName());
    }

}

结果如下(Java 1.6.0_26-b03,Ubuntu,32 位):

显然String.format() 要慢得多(一个数量级)。 StringBuffer 也比 StringBuilder 慢得多(正如我们所学的那样)。最后,StringBuilderString + 运算符几乎相同,因为它们编译为非常相似的字节码。 String.concat() 有点慢。

如果简单的串联就足够了,也不要使用String.replace()

【讨论】:

  • 如果您将"test ".concat(String.valueOf(reps)) 添加到基准测试中会很好,因为两个串联操作数的特殊情况更有可能编译成该形式(单个操作的构建器没有好处)。
  • 谢谢,非常好的测试。
  • 我跑过(Java 7、OS X),concatStringBuilder 之间的差距更大。这让我感到惊讶,concat 表面上做的工作更少(例如,只有一个char[] 分配)。在这种情况下,+ 运算符甚至是使用 StringBuilder 编译的(当然,这取决于所使用的编译器;我使用的是 Eclipse)。
  • 不要从您创建的图片中仓促得出结论,这一点非常重要!您的测试测量了 one 字符串操作的时间! (循环仅用于测试准确性。)如果您在循环中构建字符串,StringBufferStringBuilder+ 运算符快很多。示例结果:+ 运算符可能需要一分钟以上的时间来构建 500000 个字符的字符串,而 StringBu{ffer,ilder} 在不到 50 毫秒的时间内构建完全相同的字符串!
  • @steffen 是的,但是您可以执行类似builder.append(String.format(...) 的操作,以获得可读性和速度。在循环中附加到字符串确实不是一种选择。
【解决方案2】:

String.format 相对较慢,但通常足够快。

如果格式更简单,我会使用格式,除非您在分析应用程序时发现性能问题。

注意:您示例中的 String.format 需要大约 24 微秒,并且尚未完全预热。我会忽略前 10K 次迭代。

恕我直言"test " + i 在这种情况下是最简单的。

【讨论】:

    【解决方案3】:

    我已经运行了一个测试后 JVM 预热(一旦方法被编译)并获得了类似的结果,StringBuilder 快了 30 倍以上。

    格式:943
    字符串生成器:26

    public class TestPerf {
    
        private static int NUM_RUN;
    
    
        public static void main(String[] args) {
            NUM_RUN = 100_000;
            //warm up
            for (int i = 0; i < 10; i++) {
                method1();
                method2();
            }
    
            System.gc();
            System.out.println("Starting");
    
            long sum = 0;
            long start = System.nanoTime();
            for (int i = 0; i < 10; i++) {
                sum += method1();
            }
            long end = System.nanoTime();
            System.out.println("format: " + (end - start) / 1000000);
    
            System.gc();
    
            start = System.nanoTime();
            for (int i = 0; i < 10; i++) {
                sum += method2();
            }
            end = System.nanoTime();
            System.out.println("stringbuilder: " + (end - start) / 1000000);
    
            System.out.println(sum);
        }
    
        private static int method1() {
            int sum = 0;
            for (int i = 0; i < NUM_RUN; i++) {
                String s = String.format("test %d", i);
                sum += s.length();
            }
            return sum;
        }
    
        private static int method2() {
            int sum = 0;
            for (int i = 0; i < NUM_RUN; i++) {
                String s = new StringBuilder().append("test ").append(i).toString();
                sum += s.length();
            }
            return sum;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-25
      • 2015-06-04
      • 2010-10-01
      • 2019-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多