c# 字符串和stringbuilder的区别

来源:这里教程网 时间:2026-02-21 17:38:17 作者:

字符串是不可变的,每次拼接都生成新对象

在 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 +

没有绝对阈值,但以下情况强烈建议切换:

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<char></char>
+
stackalloc
拼接完成后忘记调用
ToString()
,直接传
StringBuilder
到期望
string
的 API(会触发隐式调用
ToString()
,但语义不清且可能被误读)

最常被忽略的是:不是“用了 StringBuilder 就一定快”,而是“在合适生命周期内、配合合理容量使用,才能发挥优势”。写完记得看下生成的 IL 或跑个简单 Benchmark。

相关推荐