本文探讨应用C# StringBuilder 的最佳实际,用于缩小内存调配,进步字符串操作的性能。
在 .NET 中,字符串是不可变的类型。每当你在 .NET 中批改一个字符串对象时,就会在内存中创立一个新的字符串对象来保留新的数据。相比之下,StringBuilder 对象代表了一个可变的字符串,并随着字符串大小的增长动静地扩大其内存调配。
String 和 StringBuilder 类是你在 .NET Framework 和 .NET Core 中解决字符串时常常应用的两个风行类。然而,每个类都有其长处和毛病。
BenchmarkDotNet 是一个轻量级的开源库,用于对 .NET 代码进行基准测试。BenchmarkDotNet 能够将你的办法转化为基准,跟踪这些办法,而后提供对捕捉的性能数据的洞察力。在这篇文章中,咱们将利用 BenchmarkDotNet 为咱们的 StringBuilder 操作进行基准测试。
要应用本文提供的代码示例,你的零碎中应该装置有 Visual Studio 2019 或者以上版本。
1. 在Visual Studio中创立一个控制台应用程序我的项目
首先让咱们在 Visual Studio中 创立一个 .NET Core 控制台应用程序我的项目。假如你的零碎中曾经装置了 Visual Studio 2019,请依照上面的步骤创立一个新的 .NET Core 控制台应用程序我的项目。
- 启动 Visual Studio IDE。
- 点击 "创立新我的项目"。
- 在 "创立新我的项目 "窗口中,从显示的模板列表中抉择 "控制台应用程序(.NET外围)"。
- 点击 "下一步"。
- 在接下来显示的 "配置你的新我的项目 "窗口中,指定新我的项目的名称和地位。
- 点击创立。
这将在 Visual Studio 2019 中创立一个新的 .NET Core 控制台应用程序我的项目。咱们将在本文的后续章节中应用这个我的项目来解决 StringBuilder。
2. 装置 BenchmarkDotNet NuGet包
要应用 BenchmarkDotNet,你必须装置 BenchmarkDotNet 软件包。你能够通过 Visual Studio 2019 IDE 内的 NuGet 软件包管理器,或在 NuGet 软件包管理器控制台执行以下命令来实现。
Install-Package BenchmarkDotNet
3. 应用 StringBuilderCache 来缩小调配
StringBuilderCache 是一个外部类,在 .NET 和 .NET Core 中可用。每当你须要创立多个 StringBuilder 的实例时,你能够应用 StringBuilderCache 来大大减少调配的老本。
StringBuilderCache 的工作原理是缓存一个 StringBuilder 实例,而后在须要一个新的 StringBuilder 实例时从新应用它。这缩小了调配,因为你只须要在内存中领有一个 StringBuilder 实例。
让咱们用一些代码来阐明这一点。在 Program.cs 文件中创立一个名为 StringBuilderBenchmarkDemo 的类。创立一个名为 AppendStringUsingStringBuilder 的办法,代码如下。
public string AppendStringUsingStringBuilder(){ var stringBuilder = new StringBuilder(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return stringBuilder.ToString();}
下面的代码片段显示了如何应用 StringBuilder 对象来追加字符串。接下来创立一个名为 AppendStringUsingStringBuilderCache 的办法,代码如下。
public string AppendStringUsingStringBuilderCache(){ var stringBuilder = StringBuilderCache.Acquire(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return StringBuilderCache.GetStringAndRelease(stringBuilder);}
下面的代码片段阐明了如何应用 StringBuilderCache 类的 Acquire 办法创立一个 StringBuilder 实例,而后用它来追加字符串。
上面是 StringBuilderBenchmarkDemo 类的残缺源代码供你参考。
[MemoryDiagnoser]public class StringBuilderBenchmarkDemo { [Benchmark] public string AppendStringUsingStringBuilder() { var stringBuilder = new StringBuilder(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return stringBuilder.ToString(); } [Benchmark] public string AppendStringUsingStringBuilderCache() { var stringBuilder = StringBuilderCache.Acquire(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return StringBuilderCache.GetStringAndRelease(stringBuilder); }}
你当初必须应用 BenchmarkRunner 类来指定初始终点。这是一种告诉 BenchmarkDotNet 在指定的类上运行基准的形式。
用以下代码替换 Main 办法的默认源代码。
static void Main(string[] args){ var summary = BenchmarkRunner.Run<StringBuilderBenchmarkDemo>();}
当初在 Release 模式下编译你的我的项目,并在命令行应用以下命令运行基准测试。
dotnet run -p StringBuilderPerfDemo.csproj -c Release
上面阐明了两种办法的性能差别。
正如你所看到的,应用 StringBuilderCache 追加字符串要快得多,须要的调配也少。
4. 应用 StringBuilder.AppendJoin 而不是 String.Join
String 对象是不可变的,所以批改一个 String 对象须要创立一个新的 String 对象。因而,在连贯字符串时,你应该应用 StringBuilder.AppendJoin 办法,而不是String.Join,以缩小调配,进步性能。
上面的代码列表阐明了如何应用 String.Join 和 StringBuilder.AppendJoin 办法来组装一个长字符串。
[Benchmark]public string UsingStringJoin() { var list = new List < string > { "A", "B", "C", "D", "E" }; var stringBuilder = new StringBuilder(); for (int i = 0; i < 10000; i++) { stringBuilder.Append(string.Join(' ', list)); } return stringBuilder.ToString();}[Benchmark]public string UsingAppendJoin() { var list = new List < string > { "A", "B", "C", "D", "E" }; var stringBuilder = new StringBuilder(); for (int i = 0; i < 10000; i++) { stringBuilder.AppendJoin(' ', list); } return stringBuilder.ToString();}
下图显示了这两种办法的基准测试后果。
请留神,对于这个操作,这两种办法的速度很靠近,但 StringBuilder.AppendJoin 应用的内存显著较少。
5. 应用 StringBuilder 追加单个字符
留神,在应用 StringBuilder 时,如果须要追加单个字符,应该应用 Append(char) 而不是 Append(String)。
请思考以下两个办法。
[Benchmark]public string AppendStringUsingString() { var stringBuilder = new StringBuilder(); for (int i = 0; i < 1000; i++) { stringBuilder.Append("a"); stringBuilder.Append("b"); stringBuilder.Append("c"); } return stringBuilder.ToString();}[Benchmark]public string AppendStringUsingChar() { var stringBuilder = new StringBuilder(); for (int i = 0; i < 1000; i++) { stringBuilder.Append('a'); stringBuilder.Append('b'); stringBuilder.Append('c'); } return stringBuilder.ToString();}
从名字中就能够看出,AppendStringUsingString 办法阐明了如何应用一个字符串作为 Append 办法的参数来追加字符串。
AppendStringUsingChar 办法阐明了你如何在 Append 办法中应用字符来追加字符。
下图显示了这两种办法的基准测试后果。
6. 其余 StringBuilder 优化办法
StringBuilder 容许你设置容量以进步性能。如果你晓得你要创立的字符串的大小,你能够相应地设置初始容量以大大减少内存调配。
你还能够通过应用一个可重复使用的 StringBuilder 对象池来防止调配来进步 StringBuilder 的性能。
最初,请留神,因为 StringBuilderCache是一个外部类,你须要将源代码粘贴到你的我的项目中能力应用它。回顾一下,在C#中你只能在同一个程序集或库中应用一个外部类。
因而,咱们的程序文件不能仅仅通过援用 StringBuilderCache 所在的库来拜访 StringBuilderCache 类。
这就是为什么咱们把 StringBuilderCache 类的源代码复制到咱们的程序文件中,也就是Program.cs文件。
参考资料:
- C#教程
- C#编程技术
- 编程宝库