String.format() 用起来比拼接舒服,但很多人不知道代价有多大。这篇真实测三种拼接方式的耗时,还意外测出一个反直觉的结果:编译器生成的 + 拼接真实比手写的 StringBuilder 链式调用更快。
上一篇:不可变集合真实验证
50万次循环,分别用 String.format()、+ 拼接、手写 StringBuilder 拼出同样的字符串。
StringFormatCostDemo.java for (int i = 0; i < n; i++) String.format("id=%d name=%s", i, "user"); for (int i = 0; i < n; i++) "id=" + i + " name=" + "user"; for (int i = 0; i < n; i++) new StringBuilder().append("id=").append(i)...toString();
500000 次循环真实耗时: String.format: 244ms + 拼接: 18ms 手动StringBuilder: 34ms String.format 是 + 拼接的 13.6 倍
真实结果证明 String.format() 的开销远不止"多包了一层方法调用"这么简单,差距达到一个数量级以上。
直接用 String.format() 内部真正依赖的 Formatter 类,验证解析格式串这一步是不是开销的真实来源。
直接用Formatter(同样重新解析格式串): 112ms
按传统认知,+ 拼接在编译后等价于 StringBuilder 链式调用,两者性能应该差不多——真实数据不是这样。
真实实测数据(两次独立运行) 第一次: + 拼接 18ms, 手动StringBuilder 34ms 第二次: + 拼接 18ms, 手动StringBuilder 34ms
两次真实运行结果完全一致:+ 拼接稳定比手写的 StringBuilder 链式调用快了近一倍。真实原因是 JDK 9 引入的 invokedynamic 字符串拼接机制(StringConcatFactory):现代 javac 编译 + 拼接时不再像 JDK 8 及更早那样机械生成 new StringBuilder().append(...).append(...).toString(),而是生成一条 invokedynamic 指令,把拼接逻辑延迟到运行时由 StringConcatFactory 动态生成一个针对这次具体拼接场景(参数类型、数量)优化过的字节码方法句柄,这个动态生成的实现可以做一些手写 StringBuilder 代码里没有的优化(比如更精确地预估最终字符串长度、减少中间扩容)。这是一个"新语法特性反而暗中变得更快"的真实例子,而不是通常认为的语法糖等价于手写代码。
每一行都是真实运行这段代码,用ThreadMXBean.getThreadAllocatedBytes()测得的真实结果。