资讯详情

资讯详情

C# StringBuilder 详解:字符串拼接别只会用 `+`,从原理到实战一次讲透

一句话概括string适合保存和使用文本StringBuilder更适合反复修改、不断追加的大段文本。理解它们的区别再掌握Append、Insert、Remove、Replace、Length和Capacity就能在可读性与性能之间做出更合适的选择。文章目录为什么字符串拼接多了代码可能变慢StringBuilder 是什么把“反复创建”变成“持续构建”string和StringBuilder到底有什么区别StringBuilder 的创建方式最常用的 7 组方法追加、插入、删除与替换Length、Capacity和MaxCapacity长度不等于容量实战用 StringBuilder 生成一份商品销售清单性能使用指南什么场景该用什么场景没必要新手容易踩的 6 个坑写出更稳、更清晰代码的最佳实践写在最后参考资料一、为什么字符串拼接多了代码可能变慢先看一个很常见的场景程序要把一批数据拼成一段文本例如生成日志、导出 CSV、拼接报表内容或者把串口、网络接收到的数据整理成字符串。stringresult;for(inti0;i5;i){resulti;}Console.WriteLine(result);运行结果01234代码很直观但问题在于string是不可变的immutable。字符串创建完成后不能直接修改它内部的字符内容。执行result i时程序通常需要根据旧内容和新内容生成一个新的字符串再让result指向新结果。当拼接次数很少时这种写法完全没问题但如果循环次数很多每次都根据已有文本生成新字符串就可能产生大量临时字符串和额外的内存分配。这时StringBuilder就有了用武之地。它的目标不是让所有字符串操作都变快而是让大量、反复、动态的文本构建更加合适。二、StringBuilder 是什么把“反复创建”变成“持续构建”StringBuilder是 .NET 提供的一个可变字符序列类型位于System.Text命名空间中。可以把它理解成一个“可编辑的文本工作台”先把内容一点点放进去需要时再插入、删除或替换最后统一生成普通字符串。usingSystem;usingSystem.Text;classProgram{staticvoidMain(){StringBuildersbnewStringBuilder();sb.Append(Hello);sb.Append( );sb.Append(World);stringresultsb.ToString();Console.WriteLine(result);}}输出Hello World这里有三个关键动作new StringBuilder()创建一个用于构建文本的对象。Append()把新内容追加到现有内容后面。ToString()在需要最终文本时将当前内容转换成string。和普通字符串不同多次调用Append()是在修改同一个StringBuilder实例。它会根据当前容量和新增内容的大小管理内部存储空间从而减少反复生成完整字符串的开销。记住这句话StringBuilder不是用来取代string的而是专门解决“文本需要反复构建或修改”这类问题的工具。三、string和StringBuilder到底有什么区别对比项stringStringBuilder内容是否可变不可变每次修改会得到新的字符串结果可变可在现有实例上追加、插入、删除和替换内容常见用途保存文本、简单拼接、格式化输出循环拼接大量文本、生成日志或报表、反复修改文本主要操作方式、插值字符串$...、Replace()等Append()、Insert()、Remove()、Replace()等最终结果本身就是字符串通常通过ToString()得到字符串结果是否一定更快不一定简单、固定次数的拼接通常足够不一定大量重复修改时通常更有优势3.1 为什么string被称为不可变类型stringtextHello;text World;Console.WriteLine(text);输出仍然是Hello World。这里看起来像是原来的字符串被修改了但实际含义是程序把拼接结果赋给了变量text。原来那个字符串对象的内容并没有被就地改写。3.2 为什么 StringBuilder 适合反复追加StringBuildersbnewStringBuilder();sb.Append(Hello);sb.Append( World);sb.Append(!);Console.WriteLine(sb.ToString());每次Append()都是对同一个StringBuilder实例进行操作。容量不足时它会扩展内部存储空间因此它适合“边处理数据边把文本累积起来”的任务。3.3 不是所有都有问题下面这种固定、简单的拼接通常没有必要为了性能强行改成StringBuilderstringmessage用户userName年龄age;使用插值字符串也很清晰stringmessage$用户{userName}年龄{age};真正需要关注的是拼接是否频繁、次数是否不确定、最终文本是否很大而不是看到就马上替换。四、StringBuilder 的创建方式使用前需要引用System.TextusingSystem.Text;4.1 创建空的 StringBuilderStringBuildersbnewStringBuilder();sb.Append(第一行);适用于事先不确定文本长度的情况最常见也最简单。4.2 创建时指定初始文本StringBuildersbnewStringBuilder(Hello);sb.Append( World);Console.WriteLine(sb.ToString());输出Hello World4.3 创建时指定初始容量StringBuildersbnewStringBuilder(200);sb.Append(准备生成一段较长的文本……);这里的200表示初始容量不是文本长度也不是最多只能存放 200 个字符。文本超过当前容量时StringBuilder通常会扩展内部存储空间。如果事先大致知道最终文本长度可以设置一个合适的初始容量减少扩容次数如果不知道就使用默认构造函数即可不必过早优化。五、最常用的 7 组方法追加、插入、删除与替换5.1Append()在末尾追加内容这是使用频率最高的方法可以追加字符串、数字、字符、布尔值等内容。StringBuildersbnewStringBuilder();sb.Append(编号);sb.Append(1001);sb.Append(状态);sb.Append(true);Console.WriteLine(sb.ToString());输出编号1001状态True多个Append()可以连续使用StringBuildersbnewStringBuilder();sb.Append(姓名).Append(张三).Append(年龄).Append(18);Console.WriteLine(sb.ToString());Append()返回当前StringBuilder实例的引用所以可以链式调用。链式调用只是写法更紧凑并不意味着创建了多个StringBuilder。5.2AppendLine()追加内容并换行StringBuildersbnewStringBuilder();sb.AppendLine(第一行);sb.AppendLine(第二行);sb.AppendLine(第三行);Console.WriteLine(sb.ToString());AppendLine()会追加指定内容并在末尾添加当前环境对应的换行符。需要生成多行文本、日志或报表时它通常比手动拼接\r\n更方便。5.3AppendFormat()按格式追加StringBuildersbnewStringBuilder();stringname张三;intage18;decimalscore95.5m;sb.AppendFormat(姓名{0}年龄{1}分数{2:F1},name,age,score);Console.WriteLine(sb.ToString());输出示例姓名张三年龄18分数95.5{0}、{1}、{2:F1}分别对应后面传入的参数。F1表示按一位小数格式显示。如果你更习惯插值字符串也可以把插值字符串传给Append()sb.Append($姓名{name}年龄{age}分数{score:F1});5.4Insert()在指定位置插入内容StringBuildersbnewStringBuilder(HelloWorld);sb.Insert(5, );Console.WriteLine(sb.ToString());输出Hello World第一个参数是插入位置索引从0开始。Insert(5, )的意思是在索引为5的位置插入一个空格。5.5Remove()删除指定范围的内容StringBuildersbnewStringBuilder(Hello World);sb.Remove(5,1);Console.WriteLine(sb.ToString());输出HelloWorldRemove(startIndex, length)的第一个参数表示从哪里开始第二个参数表示删除多少个字符。注意它不是结束索引。5.6Replace()批量替换内容StringBuildersbnewStringBuilder(苹果香蕉苹果橘子);sb.Replace(苹果,西瓜);Console.WriteLine(sb.ToString());输出西瓜香蕉西瓜橘子Replace()会在当前内容中替换匹配的字符或字符串。与string.Replace()类似它会返回替换后的结果区别在于StringBuilder.Replace()修改的是当前StringBuilder实例。5.7Clear()清空当前内容StringBuildersbnewStringBuilder();sb.Append(旧内容);sb.Clear();sb.Append(新内容);Console.WriteLine(sb.ToString());输出新内容Clear()会把当前内容清空使Length变成0。它通常比重新创建一个对象更方便但是否需要复用实例仍应根据代码结构和实际性能需求决定。六、Length、Capacity和MaxCapacity长度不等于容量这三个属性很容易混淆尤其是在调试StringBuilder的时候。属性含义示例理解Length当前已经包含的字符数量已经写进文本的字数Capacity当前内部存储空间可以容纳的字符数量当前文本工作台的可用容量MaxCapacity该实例允许达到的最大容量上限这个工作台允许扩展到的上限示例StringBuildersbnewStringBuilder(50);sb.Append(Hello);Console.WriteLine($内容{sb});Console.WriteLine($Length{sb.Length});Console.WriteLine($Capacity{sb.Capacity});Console.WriteLine($MaxCapacity{sb.MaxCapacity});其中Length为5初始容量为50。Capacity代表已分配存储空间的容量不代表当前已经存储了多少字符。6.1EnsureCapacity()确保容量够用StringBuildersbnewStringBuilder();intcapacitysb.EnsureCapacity(200);Console.WriteLine(capacity);EnsureCapacity(200)表示确保容量至少达到200。如果当前容量已经够用它不会无意义地缩小容量如果不够就会扩展容量。返回值是调用后的容量。6.2 修改Length需要小心StringBuildersbnewStringBuilder(Hello World);sb.Length5;Console.WriteLine(sb.ToString());输出Hello把Length设置得比当前长度小会截掉末尾字符。它不是简单的“查看长度”而是一个可以设置的属性如果把长度设得更大新增的位置会用空字符填充因此不要把它当作普通的计数变量随意修改。七、实战用 StringBuilder 生成一份商品销售清单前面学习的是单个方法现在把它们组合起来完成一个更接近实际开发的例子。假设系统需要把商品名称、单价、数量和小计整理成一份多行文本。usingSystem;usingSystem.Collections.Generic;usingSystem.Text;classProduct{publicstringName{get;set;}publicdecimalPrice{get;set;}publicintQuantity{get;set;}publicProduct(stringname,decimalprice,intquantity){Namename;Priceprice;Quantityquantity;}}classProgram{staticvoidMain(){ListProductproductsnewListProduct{newProduct(键盘,199.00m,1),newProduct(鼠标,89.50m,2),newProduct(耳机,299.00m,1)};stringreportBuildReport(products);Console.WriteLine(report);}staticstringBuildReport(ListProductproducts){StringBuildersbnewStringBuilder();decimaltotal0m;sb.AppendLine(商品销售清单);sb.AppendLine(------------------------------);foreach(Productproductinproducts){decimalsubtotalproduct.Price*product.Quantity;totalsubtotal;sb.AppendFormat(商品{0}单价{1:F2}数量{2}小计{3:F2},product.Name,product.Price,product.Quantity,subtotal);sb.AppendLine();}sb.AppendLine(------------------------------);sb.AppendFormat(总金额{0:F2},total);returnsb.ToString();}}运行结果示例商品销售清单 ------------------------------ 商品键盘单价199.00数量1小计199.00 商品鼠标单价89.50数量2小计179.00 商品耳机单价299.00数量1小计299.00 ------------------------------ 总金额677.00这个例子中BuildReport()不需要在每次循环时都把之前的整段报表重新拼接一遍而是使用同一个StringBuilder持续累积内容最后只调用一次ToString()得到完整的报告文本。这就是 StringBuilder 在实际项目中的典型使用方式先构建最后统一转换。八、性能使用指南什么场景该用什么场景没必要很多人刚学到StringBuilder就会把代码里的所有都替换掉。其实这样并不一定更好。微软文档也指出是否有明显性能收益需要结合字符串大小、修改次数和具体操作判断。8.1 推荐使用 StringBuilder 的场景在for或foreach循环中不断追加文本而且拼接次数可能较多。需要动态生成日志、报表、CSV 内容或较长的文本数据。同一段文本需要多次执行插入、删除或替换操作。最终文本长度在编写代码时无法确定且可能比较大。8.2 直接使用 string 的场景只需要拼接两三个固定片段。使用插值字符串表达一条日志或提示信息。代码更看重可读性拼接次数和内容都很少。拼接完成后主要进行查找、判断等字符串操作而不是不断修改文本。对比示例stringmessage$用户{userName}登录成功时间{DateTime.Now};这段代码清楚、直接没有必要为了形式而创建StringBuilder。而下面这种大量循环拼接就更适合考虑StringBuilderStringBuildersbnewStringBuilder();for(inti0;i10000;i){sb.Append(i).Append(,);}stringresultsb.ToString();关键不是“StringBuilder 永远更快”而是“反复构建大量文本时StringBuilder 往往更合适”。如果性能很关键应在真实数据和目标运行环境下测试而不要只凭猜测进行优化。九、新手容易踩的 6 个坑坑 1认为所有字符串拼接都必须使用 StringBuilderstringfullNamefirstName lastName;简单的固定拼接保持这样就很好。过度使用StringBuilder反而会增加对象创建和代码复杂度。坑 2忘记调用ToString()StringBuildersbnewStringBuilder();sb.Append(Hello);stringresultsb.ToString();StringBuilder本身不是string。在需要返回、保存或传递最终文本时通常要调用ToString()。坑 3把Capacity当成当前字符数Capacity表示容量Length才表示当前内容长度。即使Capacity是100当前内容也可能只有5个字符。坑 4在循环中反复调用ToString()再继续拼接StringBuildersbnewStringBuilder();for(inti0;i1000;i){sb.Append(i);Console.WriteLine(sb.ToString());}这个例子每轮都会生成当前完整文本的字符串用于输出。真实业务中如果不需要每一步的文本快照最好把ToString()放到构建结束后再调用避免反复生成中间结果。坑 5误以为 StringBuilder 可以随意跨线程共享StringBuilder实例不是为多个线程同时修改而设计的线程安全对象。如果多个线程同时对同一个实例执行Append()或其他修改操作结果可能不可预测。多线程场景中应分别构建各自的内容或通过合适的同步机制保护共享实例。坑 6把 Insert 和 Remove 当成毫无成本的操作Insert()和Remove()要调整指定位置前后的字符内容。对很长的文本反复在开头插入内容可能比从末尾追加更费时。如果能先按顺序追加最后再生成结果通常更简单也更适合StringBuilder的常见使用模式。十、写出更稳、更清晰代码的最佳实践✅按场景选择少量、固定的拼接优先保持简洁大量、重复、动态的文本构建再考虑StringBuilder。✅尽量最后转换构建过程中持续使用Append()等方法需要最终字符串时再调用ToString()。✅容量适度即可能大致预估结果长度时可以设置合理的初始容量不要为普通的小字符串盲目分配很大容量。✅方法各司其职追加用Append()追加并换行用AppendLine()插入用Insert()删除用Remove()替换用Replace()。✅分清长度与容量判断当前文本有多少字符看Length关注已分配的字符存储空间看Capacity。✅性能以测试为准不要只根据代码长短判断效率。真实的数据量、运行环境和调用方式都会影响最终表现。✅避免共享修改同一个StringBuilder不要在没有同步保护的情况下被多个线程同时修改。十一、写在最后StringBuilder看起来只是一个用于拼接字符串的类但它背后体现的是一个很实用的开发思路当数据需要反复变化时选用适合修改的工具当结果已经确定时再转换成最终需要的类型。记住三个重点就足够应对大部分日常开发string是不可变的适合保存文本和进行简单拼接。StringBuilder适合在循环或复杂流程中反复追加、插入、删除和替换文本。不要把StringBuilder当作万能优化工具代码可读性和真实性能都需要考虑。在 C# 开发中无论是生成日志、整理串口数据、拼接报表还是导出文本文件理解这几个原则都能让你的代码更加清晰、可靠。觉得有收获欢迎点赞、收藏。下次处理大量字符串时先判断场景再决定使用string还是StringBuilder比盲目追求某一种写法更重要。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →