在日常编码中,字符串拼接是高频操作,尤其是在循环中使用StringBuilder时,细节决定性能。核心优化要点清晰明了:在循环前统一初始化、预先估计所需字符容量、全程只最终获取一次结果字符串、直接追加基本类型数据、单线程环境果断选StringBuilder、多线程场景则需妥善管理实例,避免共享。

循环外初始化,避免重复创建
切忌在循环体内重复新建StringBuilder实例。每轮循环都执行 new StringBuilder() 意味着不断分配新的内部字符数组,并伴随着默认初始容量(16)的开销。即便是数十次的拼接,频繁的数组扩容和数据拷贝也会累计成显著的性能损耗。最佳实践是在循环开始前就声明并初始化StringBuilder对象,使其在整个拼接过程中得以复用。这种方式的优势在于,所有的append操作都直接作用于同一个可变的字符数组上,避免了临时对象的产生,执行效率更高。
预估并设置初始容量
StringBuilder默认初始容量较小,当拼接数据量超出时会发生扩容,这是一个涉及新数组申请和旧数据拷贝的耗时的内部过程。假设需要拼接200条日志,每条平均长度80字符,加上必要的前缀和分隔符,总长度约在18000字符。此时,直接以 new StringBuilder(18000) 的方式构造,即可在绝大多数情况下避免运行时扩容。容量估算的实用技巧是:基础固定长度 + 预期总字符数估算值 + 预留缓冲空间(通常建议增加10%-20%)。该方法能在保障性能的同时,有效控制内存占用。
单次 toString(),禁止循环内调用
必须警惕在循环内部调用 toString() 方法。该方法会基于StringBuilder当前内部的字符数组复制一份全新的String对象。若在循环中反复调用,便会产生大量临时的String对象,完全抵消了StringBuilder节省内存和提升性能的设计初衷。唯一正确的做法是,在所有数据都通过append方法添加完毕后,于循环外部仅调用一次 sb.toString() 来获取最终拼接结果。如果需要在拼接过程中查看中间状态以进行调试,可以采用 sb.length() 查看当前长度,或使用 sb.substring(0, Math.min(50, sb.length())) 截取前N个字符预览,这些操作不会引发完整数据的复制。
注意类型与线程边界
在处理数字、布尔值等基本数据类型时,应始终坚持直接调用 sb.append(i)。该方法内部已高效地完成了向字符串的转换,无需再画蛇添足地先调用 String.valueOf() 进行显式转换,后者会带来额外的自动装箱开销并生成临时String对象。关于线程安全:在完全单线程的环境下,性能优越的StringBuilder是不二之选。而在多线程并发环境下,直接将其替换为线程安全的StringBuffer并非最优解(其同步锁会引入额外开销)。更优的方案是确保每个线程使用独立的StringBuilder实例,或者借助 ThreadLocal
