setLength(0) 是最轻量安全的 StringBuilder 复用方式,仅置 count 为 0、保留数组,避免新建对象的内存分配与初始化、delete 的边界检查及扩容开销,且不引发内存泄漏。

直接用 setLength(0) 是最轻量、最安全的复用方式,比新建对象快,比 delete(0, length()) 少一次边界检查,且不触发扩容重分配。
为什么 setLength(0) 比 new StringBuilder() 更高效
新建对象要分配堆内存、初始化内部 char[] 数组(默认容量 16)、设置长度字段;而 setLength(0) 只是把当前 count 字段置为 0,内部数组完全保留,后续追加内容时能直接复用——尤其在循环中反复拼接时,避免频繁 GC 和数组拷贝。
常见误判:有人担心“不清空数组内容会导致内存泄漏”,其实不会。setLength(0) 后,toString() 或 substring() 返回的字符串只读取前 0 个字符,原数组引用仍可被 GC 回收(只要没其他强引用)。
setLength(0) 和 delete(0, length()) 的实际差异
二者语义等价,但底层实现不同:delete(0, length()) 会先校验起始/结束索引是否越界(两次比较),再调用 System.arraycopy() 把后面的内容往前挪(虽然此时长度为 0,实际不挪,但校验开销仍在);而 setLength(0) 是单条赋值指令。
- 性能上:在 JDK 8–17 中,
setLength(0)平均快 15%–25%,尤其在高频调用场景(如日志格式化循环) - 可读性上:
setLength(0)明确表达“重置长度”,而非“删掉全部”,更贴近复用意图 - 注意:若你依赖
StringBuilder的capacity()不变(比如已预估最大长度并ensureCapacity()过),setLength(0)完全满足;而delete()不影响 capacity,这点两者一致
复用时必须避开的三个坑
复用不是设了 setLength(0) 就万事大吉,以下情况会悄悄破坏复用效果或引发 bug:
- 调用
toString()后继续复用:返回的String内部可能持有对原char[]的引用(JDK 7u6 之后已优化为复制,但某些 Android 版本或老 JVM 仍存在),导致数组无法被回收。建议改用new String(sb)或确保sb生命周期可控 - 多线程共享同一个
StringBuilder实例:它不是线程安全的,setLength(0)和后续append()之间可能被其他线程插入内容。必须加锁,或改用ThreadLocal<stringbuilder></stringbuilder> - 忽略初始容量设置:如果每次拼接内容长度波动极大(比如有时 5 字符,有时 2KB),未调用
ensureCapacity()预留空间,复用反而触发多次数组扩容(Arrays.copyOf()),得不偿失。建议按典型最大长度初始化:new StringBuilder(1024)
一个典型的高复用模式示例
比如批量生成 SQL INSERT 语句:
StringBuilder sb = new StringBuilder(2048); // 预估单条最大长度
for (Record r : records) {
sb.setLength(0); // 关键:清空复用,不 new,不 delete
sb.append("INSERT INTO t VALUES (")
.append(r.id)
.append(", '")
.append(r.name.replace("'", "''"))
.append("');\n");
writer.write(sb.toString());
}
这里 setLength(0) 确保每次循环都从干净状态开始,且 StringBuilder 内部数组始终复用。若换成 new StringBuilder(2048),每轮都分配新数组,GC 压力明显上升;若漏掉预估容量,小数据时没事,大数据量时扩容抖动会拖慢整体吞吐。
真正难的不是调用 setLength(0) 这一行代码,而是想清楚:这个实例的生命周期是否可控、容量是否匹配、线程上下文是否干净——这些决定了复用到底省不省资源。

















