字符串拼接用“+”还是 StringBuilder?别再凭感觉写了

问题:拼接字符串,到底用哪个?

先问个实在的问题:你在代码里怎么拼接字符串?

很多兄弟可能是这么写的:

复制代码
string str ="Hello"+" "+"World";    //当然这里只是举个例子

也有的会在循环里这么干:

复制代码
string result = "";for (int i = 0; i < 1000; i++){    result += i.ToString(); // 循环拼接}

然后有一天,你听说了StringBuilder,据说拼接性能更好。于是你开始纠结:到底该用"+"还是StringBuilder?网上说法五花八门,有的说"+"慢成狗,有的说编译器会优化,根本不用操心。

今天咱们就把这事掰扯清楚,以后别再凭感觉写了。

结论:看场景,别迷信

一句话总结:

  • 少量、固定次数的拼接,直接用"+",代码简洁,编译器还会帮你优化。

  • 大量、循环内的拼接,尤其是次数不确定时,务必用StringBuilder,否则性能可能崩盘。

  • 特殊场景(如拼接集合、格式化)考虑用string.Concat、string.Join或字符串插值,它们底层已经做了优化。

下面展开聊聊为什么。

扩展:从内存到原理,一次讲透

1. 字符串的"不可变性"是罪魁祸首

C#里的字符串是引用类型,而且是不可变的。什么意思?就是你一旦创建了一个字符串,它就定型了,内容不能再改。当你试图"修改"它时,其实是创建了一个全新的字符串对象,原来的那个等着被垃圾回收。

比如:

复制代码
string s ="Hello";s = s +" World";

第二行执行时,先在内存里创建一个新字符串 "Hello World",然后把s指向它,原来的 "Hello" 就成了没人要的孤儿,等着GC来收。

这种设计有好处(线程安全、哈希缓存等),但拼接时就成了性能杀手。

2. "+"拼接的真相:分情况讨论

情况A:编译期就能确定的拼接

复制代码
string str ="Hello"+" "+"World";

这种代码在编译时,编译器直接把它优化成了 "Hello World",生成的IL里只有一个字符串。所以运行时没有任何拼接开销,放心用。

情况B:拼接中包含变量

复制代码
string name ="刚子";string msg ="Hello, "+ name +"!";

这种编译器会把它转成string.Concat调用,比如:

复制代码
string msg =string.Concat("Hello, ", name,"!");

string.Concat内部会根据参数数量,一次性计算出最终字符串长度,然后分配内存,把各部分拷贝进去。一次拼接,一次分配,效率其实不错。所以这种少量"+"拼接,完全没问题。

情况C:循环内反复拼接

复制代码
string result = "";for (int i = 0; i < 10000; i++){    result += i.ToString();}

这里每循环一次,都会产生一个新的字符串,而且一次比一次大。比如:

  • 第1次:长度1,分配1次

  • 第2次:长度2,新分配,拷贝之前的结果和新的字符

  • 第3次:长度3,再分配,拷贝......
    这样总共分配了10000次字符串,拷贝的总字符数大约是1+2+...+10000 ≈ 5000万次!时间复杂度O(n²),数据量大时直接卡死。

3. StringBuilder 为什么快?

StringBuilder内部维护了一个可变的字符数组(char[])。当你 Append 时,它会直接往数组里写,空间不够了就自动扩容(通常是翻倍)。所有追加操作都在同一个数组里进行,只有最后调用ToString()时才真正创建一次字符串。

所以上面的循环用StringBuilder改写:

复制代码
StringBuilder sb = new StringBuilder();for (int i = 0; i < 10000; i++){    sb.Append(i.ToString());}string result = sb.ToString();
  • 扩容次数:大约 log₂(10000) ≈ 14次(假设初始容量16)

  • 字符拷贝总量:约10000次,远小于"+"的5000万次

  • 时间复杂度O(n),性能天壤之别。

4. 来点实测数据

我写了个简单测试(环境:.NET 6, Release模式,各执行10万次拼接):

|---------------|-------|---------|--------|
| 方式 | 10次拼接 | 1000次拼接 | 10万次拼接 |
| +(循环内) | <1ms | 15ms | 爆炸(几秒) |
| StringBuilder | <1ms | <1ms | 8ms |

注意,"+"在小数量时差距不大,但一旦数量上去,直接崩盘。

5. 其他拼接方式,也得拎清楚

string.Concat

前面说了,编译器会把多个"+"优化成Concat。如果自己手动拼接一组已知的对象,用Concat比循环"+"强。

string.Join

拼接集合时最强:

复制代码
string csv = string.Join(",", numbers); // numbers是List<int>

内部也是用StringBuilder实现的,比自己手写循环高效。

字符串插值($"...")

复制代码
string msg = $"Hello, {name}! You are {age} years old.";

编译后也是string.Format(或者在某些情况下转成Concat)。它简洁明了,适合格式化场景,但如果插值数量很多且频繁调用,注意string.Format内部也有开销(解析格式字符串、参数装箱等)。不过日常用没问题。

6. 最佳实践总结

  • 原则1:能用一句话写完的拼接,直接用"+"(编译器会优化)。

  • 原则2:循环内拼接,尤其次数不确定时,无脑用StringBuilder。

  • 原则3:拼接集合用string.Join。

  • 原则4:格式化用字符串插值或string.Format,但避免在热点代码中频繁使用带复杂格式的插值。

  • 原则5:如果提前能预估最终长度,给StringBuilder预设容量(new StringBuilder(预期长度)),减少扩容。

7. 面试官追问:StringBuilder就一定比"+"快吗?

不一定。如果只是两三个字符串拼接,用"+"编译成Concat,可能比创建StringBuilder对象开销还小。毕竟new对象也有成本。所以"少量固定次数"时,"+"更优。

另外,如果你用的是.NET Core 2.1+,string.Create方法允许你直接操作字符数组,实现最高效的拼接,但那是高阶玩法,日常用不上。

总结

字符串拼接这事,看起来小,但用错了地方,真能把程序拖垮。别再凭感觉了,记住三个关键词:少量用"+"、循环用Builder、集合用Join。写出性能好的代码,从选对拼接方式开始。


我是码农刚子,如果觉得本文对你有帮助,欢迎点赞👍、转发、关注,让更多小伙伴少走弯路!
#字符串拼接 #StringBuilder #dotNet #csharp

相关推荐
.NET修仙日记2 小时前
Acme .NET 工具类库:一站式解决.NET开发高频场景问题
.net·nuget·acme·.net8.0·.net9.0·acme.net·.net10.0
.NET修仙日记3 小时前
Acme.ReturnOh:让.NET API返回值处理更优雅,统一响应格式一步到位
c#·.net·webapi
喵叔哟5 小时前
19-AIAgent智能代理开发
微服务·.net
唐青枫7 小时前
深入理解 C#.NET TaskScheduler:为什么大量使用 Work-Stealing
c#·.net
喵叔哟8 小时前
20-多模态AI应用开发
人工智能·微服务·.net
桑榆肖物8 小时前
.NET 10 Native AOT 在 Linux 嵌入式设备上的实战
java·linux·.net·aot
我是唐青枫9 小时前
深入理解 C#.NET Task.Run:调度原理、线程池机制与性能优化
性能优化·c#·.net
步步为营DotNet9 小时前
深入剖析.NET 11中Microsoft.Extensions.AI的应用与优化 前言
人工智能·microsoft·.net
江沉晚呤时9 小时前
基于 AssemblyLoadContext 的 .NET 插件化架构设计与实现
开发语言·c#·.net