String.format和StringBuilder之间的性能差异

47

为了连接字符串,我们通常使用StringBuilder而不是String+String。但是同样我们也可以使用String.format来实现相同的功能,它通过给定的区域设置、格式和参数返回格式化后的字符串。

例如:

使用StringBuilder连接字符串:

String concatenateStringWithStringBuilder(String name, String lName, String nick) {
    final StringBuilder sb = new StringBuilder("Contact {");
    sb.append(", name='").append(name)
      .append(", lastName='").append(lName)
      .append(", nickName='").append(nick)
      .append('}');
    return sb.toString();
}

使用StringFormat连接字符串:

String concatenateStringWithStringFormat(String name, String lName, String nick) {
    return String.format("Contact {name=%s, lastName=%s, nickName=%s}", name, lName, nick);
}

在性能方面,String.FormatStringBuilder 的效率一样吗?哪一个更适合连接字符串,为什么?

更新

我查看了类似的问题,但没有回答我的问题。到目前为止,我一直使用StringBuilder来连接字符串,我应该继续使用它吗?还是应该使用String.format?问题是哪个更好,为什么?


一个快速的基准测试不会花费太多时间。 - WoooHaaaa
2
第三个选项:return "Contact {name=" + name + ", lastName=" + lName + ", nickName=" + nick + '}';,这将创建与使用StringBuilder相同的字节码,并且与使用String.Format一样易读。 - Klitos Kyriacou
如果性能很重要,我应该使用Java的String.format()吗? - Binu
3
除非你的方法每秒被调用数千次,否则你不会注意到性能差异。你不应该使用StringBuilder或StringBuffer,因为它们可读性较差。我建议使用String.format,因为它可以清晰地展示最终字符串的样式。 - VGR
3个回答

38

"更好"一词完全取决于您的需求:

  • 例如,String Builder会更快,但代码会更难以阅读,并且更容易出错。

  • 另一方面,String.format()可以生成更易于阅读的代码,但性能会有所损失。

JMH基准测试用于说明性能差异(请注意,字符串构建器代码更长,很难理解生成的字符串的外观):

@Fork(1)
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Measurement(iterations = 10)
@Warmup(iterations = 10)
@BenchmarkMode(Mode.Throughput)
public class StringFormatBenchmark {
    private String name = "UserName";
    private String lName = "LUserName";
    private String nick = "UserNick";

    @Benchmark
    public void stringFormat(Blackhole blackhole) {
        final String result = String.format("Contact {name=%s, lastName=%s, nickName=%s}", name, lName, nick);
        blackhole.consume(result);
    }

    @Benchmark
    public void stringBuilder(Blackhole blackhole) {
        final StringBuffer sb = new StringBuffer("Contact {");
        sb.append(", name='").append(name)
                .append(", lastName='").append(lName)
                .append(", nickName='").append(nick)
                .append('}');
        final String result = sb.toString();
        blackhole.consume(result);
    }
}

结果如下:

Benchmark                             Mode  Cnt      Score     Error   Units
StringFormatBenchmark.stringBuilder  thrpt   10  10617.210 ± 157.302  ops/ms
StringFormatBenchmark.stringFormat   thrpt   10    960.658 ±   7.398  ops/ms

对于非性能关键的代码,我更喜欢使用String.format(),因为它更容易、更愉悦地使用。此外,只需查看格式,就可以清楚地看到生成的字符串的样子。如果我正在编写性能关键的代码或需要低GC影响的代码,我会使用StringBuilder,因为它更快并且可以重复利用。


请问您是如何运行它的? - Faraz
@FarazDurrani 只需将测试类名称作为参数运行“Launcher”即可:https://github.com/SvetlinZarev/com.github.svetlinzarev.jmh.benchmarks/blob/master/src/main/java/com/github/svetlinzarev/jmh/benchmarks/Launcher.java - Svetlin Zarev
谢谢你,@Svetlin。我已经可以从 Google 运行其他的 JMH 了。还是非常感谢。 - Faraz
3
我认为StringBuilder更易读,且出错率更低。我不习惯String.format()的符号。在使用String.format()时,我需要更加注意项目的顺序,而在StringBuilder中,每行只有一个项目,更加有组织。 - The Student

20

在使用StringBuilderString.format进行一些小测试之后,我了解到它们解决字符串拼接所需的时间有多长。以下是代码片段和结果:

代码:

String name = "stackover";
String lName = " flow";
String nick = " stackoverflow";
String email = "stackoverflow@email.com";
int phone = 123123123;

//for (int i = 0; i < 10; i++) {
long initialTime1 = System.currentTimeMillis();
String response = String.format(" - Contact {name=%s, lastName=%s, nickName=%s, email=%s, phone=%d}",
                                name, lName, nick, email, phone);
long finalTime1 = System.currentTimeMillis();
long totalTime1 = finalTime1 - initialTime1;
System.out.println(totalTime1 + response);

long initialTime2 = System.currentTimeMillis();
final StringBuilder sb = new StringBuilder(" - Contact {");
sb.append("name=").append(name)
  .append(", lastName=").append(lName)
  .append(", nickName=").append(nick)
  .append(", email=").append(email)
  .append(", phone=").append(phone)
  .append('}');
String response2 = sb.toString();
long finalTime2 = System.currentTimeMillis();
long totalTime2 = finalTime2 - initialTime2;
System.out.println(totalTime2 + response2);
//}

运行代码多次后,我发现String.format花费了更多的时间:

String.format: 46: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
String.format: 38: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
String.format: 51: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}

但是,如果我将相同的代码放在一个循环内运行,结果会改变。

String.format: 43: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
String.format: 1: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
String.format: 1: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}

首次运行String.format需要更多时间,之后的时间会更短,尽管由于StringBuilder的结果并不稳定。

正如@G.Fiedler所说:“String.format必须解析格式字符串…”

根据这些结果可以说StringBuilderString.format更有效率。


你也可以在长字符串和短字符串上测试 String.concat - Ferrybig
2
有趣的测试循环的想法。似乎格式化字符串解析被缓存并重复使用,而不是在每次循环迭代时重新解析。 此外,由于您在每次迭代中都实例化了全新的StringBuilder(假设您只是将相同的代码包装在一个循环中),因此在随后的循环迭代中StringBuilder会变慢,这是有道理的。 - Kingand

13

StringBuilder更快,因为String.format需要解析格式字符串(一种复杂的领域特定语言),这是很昂贵的。

使用StringBuilder代替String + String

顺便说一下:自从Java1.5以来这两种方式生成的字节码相同。


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