StringBuilder.Append()是唯一推荐的拼接方式;避免在循环中混用+和StringBuilder,短字符串拼接用+更快,栈分配Span<char>适用于拼完即弃场景。

StringBuilder.Append() 是唯一该用的拼接方式
直接调用 Append(),别碰 AppendLine() 或 Insert() 做主拼接逻辑——前者多一次换行判断,后者触发内部数组搬移。真实场景里,90% 的低效都源于在循环里混用 + 和 StringBuilder,比如:
var sb = new StringBuilder();<br>for (int i = 0; i < list.Count; i++) {<br> sb.Append(list[i] + ", "); // 错!这里先用 + 拼了两个字符串,再塞进 sb<br>}这等于白建 StringBuilder。
正确做法是拆干净:所有子片段单独 Append(),中间分隔符也单独 Append():
- 用
foreach遍历,避免索引越界或漏首尾 - 如果确定长度,构造时传入容量:
new StringBuilder(estimatedLength),省去多次扩容复制 - 避免在
Append()里调用.ToString()、.Trim()等返回新字符串的方法
ToString() 只在最终输出前调用一次
ToString() 不是“取值”,而是触发内部字符数组拷贝成新 string。它开销不小,尤其当 StringBuilder 已累积几 MB 内容时。常见错误是边拼边转:
sb.Append("key:").Append(key).Append(", value:").Append(value);<br>Log(sb.ToString()); // 错!每次 log 都拷贝一整份<br>sb.Clear();
更糟的是有人把它当调试手段,在循环里反复 ToString() 查看中间状态——这会让性能跌回 + 拼接水平。
- 只在真正需要字符串(比如写文件、返回 API、正则匹配)时调用一次
ToString() - 调试用
sb.Length和sb.Capacity看规模,比ToString()快百倍 - 若需复用,用
sb.Clear(),不是新建对象;但注意Clear()不释放底层数组,只是重置长度
Replace() 和 Remove() 会破坏 StringBuilder 的高效性
Replace() 和 Remove() 在 StringBuilder 里不是 O(1),它们要扫描、搬移、重建内部缓冲区。一旦出现,就说明设计有误——StringBuilder 本质是“只追加”结构,不是文本编辑器。
典型翻车现场:
sb.Append(line);<br>sb.Replace("\t", " "); // 错!为格式化牺牲效率<br>sb.Replace("\r\n", "\n");这类操作应该前置处理原始数据,或者改用 string.Replace() 处理单行后再塞进 StringBuilder。
- 批量替换需求,优先考虑用正则预处理源数据,再拼接
- 真要删某段,用
sb.Remove(startIndex, length)比循环DeleteCharAt()强,但依然应尽量避免 - 如果频繁修改中间内容,
StringBuilder不适合你,换List<string>+string.Join()更稳
.NET 6+ 的 string.Create() 是更底层的替代方案
当拼接逻辑极其固定(比如日志模板、JSON 片段)、且对 GC 压力敏感时,string.Create() 比 StringBuilder 少一次内存拷贝。但它要求你精确知道最终长度,并手动写入每个字符:
string result = string.Create(bufferLength, (list, index), (span, state) => {<br> var (list, index) = state;<br> list[index].AsSpan().CopyTo(span);<br> span[list[index].Length] = (byte)',';<br>});
这不是日常推荐方案,而是给高性能库作者留的出口。绝大多数业务代码里,写错 span 索引比写错 Append() 更难调试。
- 仅当
StringBuilder.ToString()占用 profiler 中 >5% 的分配量时才考虑 - 必须配合
Span<char>和明确长度计算,没经验容易越界或截断 - 不支持 Unicode 替代对、组合字符等复杂 case,纯 ASCII 场景最安全
拼接本身不难,难的是什么时候不该用 StringBuilder——比如拼 3 个短字符串,用 + 反而更快;又比如拼完立刻丢弃,用 Span<char> 栈分配更省。这些边界得看实际 profile 数据,不是凭感觉。










