本文探讨应用 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# 编程技术
- 编程宝库