C#中字符串不可变,频繁拼接会大量分配临时对象并加重GC;StringBuilder通过可变字符数组和容量预估优化高频拼接,适用于循环拼接、结构化文本生成等场景,但需避免误用如小规模拼接或未复用实例。

字符串是不可变的,每次拼接都生成新对象
在 C# 中,string 是引用类型,但设计为不可变(immutable)。这意味着任何看似“修改”字符串的操作——比如 +、+=、Substring()、Replace()——实际都会创建一个全新的 string 实例,原字符串不变。
这在少量拼接时没问题,但循环中反复拼接(例如构建日志、生成 HTML、解析文本)会导致大量临时字符串对象被分配到堆上,触发频繁 GC,性能明显下降。
- 1000 次
+=拼接可能产生 1000 个中间string对象 - 内存占用和 GC 压力随拼接次数呈线性甚至超线性增长
- 调试时用内存分析器(如 Visual Studio Diagnostic Tools)能清晰看到这些短生命周期字符串
StringBuilder 是可变缓冲区,专为高频拼接优化
StringBuilder 内部维护一个字符数组(char[]),通过预分配容量和就地追加来避免重复分配。它不是用来替代所有字符串操作的,而是解决「多次、动态、未知长度」拼接场景的工具。
关键点在于它的容量管理:
- 默认初始容量是 16,超出时自动扩容(通常翻倍),但扩容本身有开销
- 如果能预估最终长度,用
new StringBuilder(estimatedCapacity)可避免多次扩容 -
ToString()才真正生成一个string;之前所有Append()、Insert()都不产生新字符串
StringBuilder sb = new StringBuilder(256); // 预分配 256 字符空间
sb.Append("User: ").Append(name).Append(", ID: ").Append(id);
string result = sb.ToString(); // 仅此处生成 string什么时候该用 StringBuilder 而不是 string +
没有绝对阈值,但以下情况强烈建议切换:
一套专门解决旅行社网上预定、发布、管理线路的强大系统,系统基于ASP+ACCESS数据库开发,功能强大,操作方便,系统设计完全符合旅行社的运做模式。系统着重体现易操作性,只要您会打字,便操作。系统由以下几个模块组成:1、线路的类别发布和管理2、线路的发布和管理3、线路的属性管理(是精品线路、还是普通线路)4、客户预定线路订单管理,人性化的区分为未处理订但和处理订单5、线路查询功能6、网站留言功能,
- 在
for或foreach循环中做字符串拼接(尤其迭代次数 > 5–10) - 构建 SQL 查询、JSON 片段、XML 片段等结构化文本
- 日志聚合、模板渲染、CSV 行拼接等 I/O 前的组装环节
- 调用
string.Concat()或string.Join()无法覆盖的复杂逻辑(如条件插入、嵌套格式)
反例:拼接固定两三个变量,如 $"Hello {name}" 或 "a" + b + "c" —— 编译器会优化为 string.Concat,比 StringBuilder 更轻量。
常见误用和陷阱
用错场景或方式反而会降低性能或引入 bug:
- 每次只
Append()一两个字符却反复新建StringBuilder实例(应复用实例或改用string) - 忽略容量预估,导致小字符串拼接时因默认扩容策略(16→32→64…)浪费内存
- 多线程共享同一个
StringBuilder实例而未加锁(它不是线程安全的;需要并发时考虑lock或改用System.Text.StringBuilder的线程安全替代方案,如Span+stackalloc) - 拼接完成后忘记调用
ToString(),直接传StringBuilder到期望string的 API(会触发隐式调用ToString(),但语义不清且可能被误读)
最常被忽略的是:不是“用了 StringBuilder 就一定快”,而是“在合适生命周期内、配合合理容量使用,才能发挥优势”。写完记得看下生成的 IL 或跑个简单 Benchmark。









