提高C# StringBuilder操作性能优化的方法

 更新时间:2021年11月19日 16:46:21   作者:编程宝库  
本篇文章主要介绍使用C# StringBuilder 的项目实践,用于减少内存分配,提高字符串操作的性能。对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

本文探讨使用C# StringBuilder 的最佳实践,用于减少内存分配,提高字符串操作的性能。

在 .NET 中,String 对象是不可改变的。每次使用 System.String 类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。

在需要对字符串执行重复修改的情况下,与创建新的 String 对象相关的系统开销可能会非常昂贵。如果要修改字符串而不创建新的对象,则可以使用 System.Text.StringBuilder 类。例如,当在一个循环中将许多字符串连接在一起时,使用 StringBuilder 类可以提升性能。

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. 编程宝库

ASP.NET提高StringBuilder类操作性能就向你介绍到这里,希望对你有所帮助。

相关文章

  • C#设计模式之职责链模式示例详解

    C#设计模式之职责链模式示例详解

    这篇文章主要给大家介绍了关于C#设计模式之职责链模式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • 通过C#实现自动售货机接口

    通过C#实现自动售货机接口

    这篇文章主要介绍了通过C#实现自动售货机接口,需要的朋友可以参考下
    2015-07-07
  • C#中数据的传递以及ToolStripProgressBar

    C#中数据的传递以及ToolStripProgressBar

    本文主要介绍了C#的数据传递方法以及ToolStripProgressBar进度条的使用。希望对大家有所帮助,话不多说,请看下面代码
    2016-11-11
  • 浅谈C#索引器

    浅谈C#索引器

    这篇文章主要简单介绍C#索引器,索引器使你可从语法上方便地创建类、结构或接口,以便客户端应用程序可以像访问数组一样访问它们。编译器将生成一个 Item 属性和适当的访问器方法,在主要目标是封装内部集合或数组的类型中,常常要实现索引器,下面我们一起来看看具体内容吧
    2021-11-11
  • C#6.0中10大新特性的应用和总结

    C#6.0中10大新特性的应用和总结

    微软发布C#6.0、VS2015等系列产品也有一段时间了,但是网上的教程却不多,这里真对C#6.0给大家做了一些示例,分享给大家。
    2016-03-03
  • C# 超高面试题收集整理

    C# 超高面试题收集整理

    C# 超高面试题,学习c sharp的朋友可以看下,有说明地方的不足。是不是所有问题都有自己的解决方法。
    2010-03-03
  • C#时间戳基本用法实例分析

    C#时间戳基本用法实例分析

    这篇文章主要介绍了C#时间戳基本用法,较为详细的讲述了时间戳的概念、生成方法与转换技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • C#/VB.NET实现在PDF文档中创建表格

    C#/VB.NET实现在PDF文档中创建表格

    表格是一种直观高效的数据展示方式,可以按行和列的形式呈现数据,从而更容易吸引读者的注意,本文将介绍如何使用 Spire.PDF for .NET 通过 .NET 程序在 PDF 文档中创建表格,需要的可以参考下
    2023-12-12
  • WPF实现图片合成或加水印的方法【2种方法】

    WPF实现图片合成或加水印的方法【2种方法】

    这篇文章主要介绍了WPF实现图片合成或加水印的方法,结合实例形式分析了2种比较实用的WPF图片操作相关技巧,需要的朋友可以参考下
    2017-03-03
  • 如何使用C#在PDF文件添加图片印章

    如何使用C#在PDF文件添加图片印章

    文档中添加印章可以起一定的作用,比如,防止文件随意被使用,或者确保文档内容的安全性和权威性。C#添加图片印章其实也有很多实现方法,这里我使用的是免费的第三方软件Free Spire.PDF,向大家阐述如何以编程的方式在PDF文件中添加图片印章
    2017-01-01

最新评论