本文探讨使用C#StringBuilder的最佳实践,用于减少内存分配,提高字符串操作的性能。 在.NET中,字符串是不可变的类型。每当你在.NET中修改一个字符串对象时,就会在内存中创建一个新的字符串对象来保存新的数据。相比之下,StringBuilder对象代表了一个可变的字符串,并随着字符串大小的增长动态地扩展其内存分配。 String和StringBuilder类是你在.NETFramework和.NETCore中处理字符串时经常使用的两个流行类。然而,每个类都有其优点和缺点。 BenchmarkDotNet是一个轻量级的开源库,用于对.NET代码进行基准测试。BenchmarkDotNet可以将你的方法转化为基准,跟踪这些方法,然后提供对捕获的性能数据的洞察力。在这篇文章中,我们将利用BenchmarkDotNet为我们的StringBuilder操作进行基准测试。 要使用本文提供的代码示例,你的系统中应该安装有VisualStudio或者以上版本。 1.在VisualStudio中创建一个控制台应用程序项目首先让我们在VisualStudio中创建一个.NETCore控制台应用程序项目。假设你的系统中已经安装了VisualStudio,请按照下面的步骤创建一个新的.NETCore控制台应用程序项目。 1.启动VisualStudioIDE。 2.点击"创建新项目"。 3.在"创建新项目"窗口中,从显示的模板列表中选择"控制台应用程序(.NET核心)"。 4.点击"下一步"。 5.在接下来显示的"配置你的新项目"窗口中,指定新项目的名称和位置。 6.点击创建。 这将在VisualStudio中创建一个新的.NETCore控制台应用程序项目。我们将在本文的后续章节中使用这个项目来处理StringBuilder。 2.安装BenchmarkDotNetNuGet包要使用BenchmarkDotNet,你必须安装BenchmarkDotNet软件包。你可以通过VisualStudioIDE内的NuGet软件包管理器,或在NuGet软件包管理器控制台执行以下命令来完成。 1 Install-PackageBenchmarkDotNet 3.使用StringBuilderCache来减少分配StringBuilderCache是一个内部类,在.NET和.NETCore中可用。每当你需要创建多个StringBuilder的实例时,你可以使用StringBuilderCache来大大减少分配的成本。 StringBuilderCache的工作原理是缓存一个StringBuilder实例,然后在需要一个新的StringBuilder实例时重新使用它。这减少了分配,因为你只需要在内存中拥有一个StringBuilder实例。 让我们用一些代码来说明这一点。在Program.cs文件中创建一个名为StringBuilderBenchmarkDemo的类。创建一个名为AppendStringUsingStringBuilder的方法,代码如下。 publicstringAppendStringUsingStringBuilder(){varstringBuilder=newStringBuilder();stringBuilder.Append("FirstString");stringBuilder.Append("SecondString");stringBuilder.Append("ThirdString");returnstringBuilder.ToString();} 上面的代码片段显示了如何使用StringBuilder对象来追加字符串。接下来创建一个名为AppendStringUsingStringBuilderCache的方法,代码如下。 publicstringAppendStringUsingStringBuilderCache(){varstringBuilder=StringBuilderCache.Acquire();stringBuilder.Append("FirstString");stringBuilder.Append("SecondString");stringBuilder.Append("ThirdString");returnStringBuilderCache.GetStringAndRelease(stringBuilder);} 上面的代码片段说明了如何使用StringBuilderCache类的Acquire方法创建一个StringBuilder实例,然后用它来追加字符串。 下面是StringBuilderBenchmarkDemo类的完整源代码供你参考。 [MemoryDiagnoser]publicclassStringBuilderBenchmarkDemo{[Benchmark]publicstringAppendStringUsingStringBuilder(){varstringBuilder=newStringBuilder();stringBuilder.Append("FirstString");stringBuilder.Append("SecondString");stringBuilder.Append("ThirdString");returnstringBuilder.ToString();}[Benchmark]publicstringAppendStringUsingStringBuilderCache(){varstringBuilder=StringBuilderCache.Acquire();stringBuilder.Append("FirstString");stringBuilder.Append("SecondString");stringBuilder.Append("ThirdString");returnStringBuilderCache.GetStringAndRelease(stringBuilder);}} 你现在必须使用BenchmarkRunner类来指定初始起点。这是一种通知BenchmarkDotNet在指定的类上运行基准的方式。 用以下代码替换Main方法的默认源代码。 staticvoidMain(string[]args){varsummary=BenchmarkRunner.RunStringBuilderBenchmarkDemo();} 现在在Release模式下编译你的项目,并在命令行使用以下命令运行基准测试。 dotnetrun-pStringBuilderPerfDemo.csproj-cRelease ?下面说明了两种方法的性能差异。 正如你所看到的,使用StringBuilderCache追加字符串要快得多,需要的分配也少。 4.使用StringBuilder.AppendJoin而不是String.JoinString对象是不可变的,所以修改一个String对象需要创建一个新的String对象。因此,在连接字符串时,你应该使用StringBuilder.AppendJoin方法,而不是String.Join,以减少分配,提高性能。 下面的代码列表说明了如何使用String.Join和StringBuilder.AppendJoin方法来组装一个长字符串。 [Benchmark]publicstringUsingStringJoin(){varlist=newListstring{"A","B","C","D","E"};varstringBuilder=newStringBuilder();for(inti=0;i;i++){stringBuilder.Append(string.Join(,list));}returnstringBuilder.ToString();}[Benchmark]publicstringUsingAppendJoin(){varlist=newListstring{"A","B","C","D","E"};varstringBuilder=newStringBuilder();for(inti=0;i;i++){stringBuilder.AppendJoin(,list);}returnstringBuilder.ToString();}
下图显示了这两种方法的基准测试结果。 请注意,对于这个操作,这两种方法的速度很接近,但StringBuilder.AppendJoin使用的内存明显较少。 5.使用StringBuilder追加单个字符注意,在使用StringBuilder时,如果需要追加单个字符,应该使用Append(char)而不是Append(String)。 请考虑以下两个方法。 [Benchmark]publicstringAppendStringUsingString(){varstringBuilder=newStringBuilder();for(inti=0;i;i++){stringBuilder.Append("a");stringBuilder.Append("b");stringBuilder.Append("c");}returnstringBuilder.ToString();}[Benchmark]publicstringAppendStringUsingChar(){varstringBuilder=newStringBuilder();for(inti=0;i;i++){stringBuilder.Append(a);stringBuilder.Append(b);stringBuilder.Append(c);}returnstringBuilder.ToString();} 从名字中就可以看出,AppendStringUsingString方法说明了如何使用一个字符串作为Append方法的参数来追加字符串。 AppendStringUsingChar方法说明了你如何在Append方法中使用字符来追加字符。 下图显示了这两种方法的基准测试结果。 6.其他StringBuilder优化方法StringBuilder允许你设置容量以提高性能。如果你知道你要创建的字符串的大小,你可以相应地设置初始容量以大大减少内存分配。 你还可以通过使用一个可重复使用的StringBuilder对象池来避免分配来提高StringBuilder的性能。 最后,请注意,由于StringBuilderCache是一个内部类,你需要将源代码粘贴到你的项目中才能使用它。回顾一下,在C#中你只能在同一个程序集或库中使用一个内部类。 因此,我们的程序文件不能仅仅通过引用StringBuilderCache所在的库来访问StringBuilderCache类。 这就是为什么我们把StringBuilderCache类的源代码复制到我们的程序文件中,也就是Program.cs文件。 出处:
|