本文探讨使用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 控制台应用程序项目。

  • 1. 启动 Visual Studio IDE。
  • 2. 点击 "创建新项目"。
  • 3. 在 "创建新项目 "窗口中,从显示的模板列表中选择 "控制台应用程序(.NET核心)"。
  • 4. 点击 "下一步"。
  • 5. 在接下来显示的 "配置你的新项目 "窗口中,指定新项目的名称和位置。
  • 6. 点击创建。

这将在 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文件。

参考资料:

1. C#教程

2. C#编程技术

3. 编程宝库

如何提高C# StringBuilder的性能的更多相关文章

  1. 使用内存虚拟硬盘 提高ArcGIS server并发性能的一种方法

    1 问题提出 1.1 概述 提高ArcGIS server并发性能的方法很多,本文讨论在用户硬件足够强大的情况下(主要是内存足够大),使用内存模拟硬盘来提高数据的读取效率,以达到提高ArcGIS se ...

  2. 提高 Linux 上 socket 性能

      http://www.cnblogs.com/luxf/archive/2010/06/13/1757662.html 基于Linux的Socket网络编程的性能优化   1 引言    随着In ...

  3. 使用Zend OpCache 提高 PHP 5.5+ 性能

    使用Zend OpCache 提高 PHP 5.5+ 性能 作者:admin | 时间:February 28, 2015 | 分类:Linux | 评论:1 评论 PHP 5.5 以后内建了 OpC ...

  4. 使用异步 I/O 大大提高应用程序的性能

    使用异步 I/O 大大提高应用程序的性能 学习何时以及如何使用 POSIX AIO API Linux® 中最常用的输入/输出(I/O)模型是同步 I/O.在这个模型中,当请求发出之后,应用程序就会阻 ...

  5. 走向DBA[MSSQL篇] - 从SQL语句的角度提高数据库的访问性能(转)

    最近公司来一个非常虎的DBA,10几年的经验,这里就称之为蔡老师吧,在征得我们蔡老同意的前提下 ,我们来分享一下蔡老给我们带来的宝贵财富,欢迎其他的DBA来拍砖.  目录 1.什么是执行计划?执行计划 ...

  6. 通过硬件层提高Android动画的性能

    曾有许多人问我为什么在他们开发的应用中,动画的性能表现都很差.对于这类问题,我往往会问他们:你们有尝试过在硬件层解决动画的性能问题么? 我们都知道,在播放动画的过程中View在每一帧动画的显示时重绘自 ...

  7. [MSSQL]从SQL语句的角度 提高数据库的访问性能

    1.什么是执行计划?执行计划是依赖于什么信息. 2. 统一SQL语句的写法减少解析开销 3. 减少SQL语句的嵌套 4. 使用“临时表”暂存中间结果 5. OLTP系统SQL语句必须采用绑定变量 6. ...

  8. 走向DBA[MSSQL篇] 从SQL语句的角度 提高数据库的访问性能

    原文:走向DBA[MSSQL篇] 从SQL语句的角度 提高数据库的访问性能 最近公司来一个非常虎的dba  10几年的经验 这里就称之为蔡老师吧 在征得我们蔡老同意的前提下  我们来分享一下蔡老给我们 ...

  9. 修改Linux内核参数提高Nginx服务器并发性能

    当linux下Nginx达到并发数很高,TCP TIME_WAIT套接字数量经常达到两.三万,这样服务器很容易被拖死.事实上,我们可以简单的通过修改Linux内核参数,可以减少Nginx服务器 的TI ...

随机推荐

  1. WinForm事件与消息

    WinForm事件与消息 消息概述以及在C#下的封装 Windows下应用程序的执行是通过消息驱动的.所有的外部事件,如键盘输入.鼠标移动.按动鼠标都由OS系统转换成相应的"消息" ...

  2. 活动回顾|ShardingSphere X openGauss,将会产生怎样的化学反应?

    "ShardingSphere 作为 openGauss 生态的开源分布式数据库解决方案,将持续助力于 openGauss,满足千行百业广大客户分布式场景需求." 5月29日,由  ...

  3. Spark分区器浅析

    分区器作用:决定该数据在哪个分区 概览: 仅仅只有pairRDD才可能持有分区器,普通RDD的分区器为None 在分区器为None时RDD分区一般继承至父RDD分区 初始RDD分区数: 由集合创建,R ...

  4. 【UE4 C++】Slate 初探: Editor UI 与 Game UI

    概述 名词区分 Slate Slate 是完全自定义.与平台无关的UI框架 应用 可用于编辑器UI,编辑器的大部分界面都是使用 Slate 构建的 可做为游戏UI 可作为独立应用开发 只能 C++ 开 ...

  5. UML图 | 时序图(顺序、序列图)绘制

    上一次写过一篇 UML | 类图 相关的文章,平时规范开发会用的上,或者是写什么文档,就还是需要画图,就像毕业设计就是如此.希望能够帮助到大家. 注:本文中所用画图软件为 Microsoft Visi ...

  6. 第0次 Beta Scrum Meeting

    本次会议为Beta阶段第0次Scrum Meeting会议 会议概要 会议时间:2021年5月27日 会议地点:「腾讯会议」线上进行 会议时长:1小时 会议内容简介:本次会议为Beta阶段启程会议,主 ...

  7. Linux中检查字符串是否为合法IP地址的shell脚本

    #!/bin/bash #判断IP地址是否为有效IP CHKECK_IP () { CHECK_STEP1=`echo $1 | awk -F"." '{print NF}'` i ...

  8. netty中使用protobuf实现多协议的消息

    在我们使用 netty 的过程中,有时候为了高效的传输数据,经常使用 protobuf 进行数据的传输,netty默认情况下为我们实现的 protobuf 的编解码,但是默认的只能实现单个对象的编解码 ...

  9. TCP 拥塞窗口原理

    学过网络相关课程的,都知道TCP中,有两个窗口: 滑动窗口(在我们的上一篇文章中有讲),接收方通过通告发送方自己的可以接受缓冲区大小(这个字段越大说明网络吞吐量越高),从而控制发送方的发送速度. 拥塞 ...

  10. Spring:所有依赖项注入的类型

    一.前言 Spring文档严格只定义了两种类型的注入:构造函数注入和setter注入.但是,还有更多的方式来注入依赖项,例如字段注入,查找方法注入.下面主要是讲使用Spring框架时可能发生的类型. ...