StringBuilder和'+'运算符

6

我将要维护这段代码,它经常有以下类似的模式:

StringBuilder result = new StringBuilder();

result.Append("{=" + field.Name + "={");

这种写法会导致很多无用的对象构造,看起来很浪费,我想要重写成这样:

result.Append("{=").Append(field.Name).Append("={");

第一个版本是否会给GC带来更大的负担是正确的吗?或者在C#编译器中有一些优化方法,使得将字符串与字符串字面量连接不会创建临时对象?


6
编译器足够聪明,能够生成对 String.Concat() 重载方法之一的调用。如果你仅使用 StringBuilder 进行字符串拼接,则实际上效果更差。 - Hans Passant
1
你只是用StringBuilder来做这件事吗?如果是的话,那就太浪费了。直接使用字符串拼接即可。 - Ant P
我不确定,但我听说如果您要连接的对象数量在编译时固定,则会进行良好的优化,不会浪费内存。其次,StringBuilder可能会比短字符串连接浪费更多的内存和CPU时间,因此最好连接几个短字符串,而不是为它们构建一个StringBuilder。 - Sasha
1
"它。就是。无所谓!"(几乎大部分时间) - Jonas Elfström
是的,它是在一个循环内完成的,并且有比那一行更多的字符串连接。在大多数应用程序中,我不会关心这种微小的优化,但在这个应用程序中,它必须具有小于1秒的响应时间,并且我尽可能避免(完全)垃圾收集。除此之外,如果这种类型的微调代码自然而然地出现在你的手中,它不会伤害任何人。 - Serve Laurijssen
显示剩余5条评论
3个回答

6
我同意所有的答案,但是我认为您需要了解C#中字符串的使用方式以及它们在“底层”的实际操作方式。当有5个或更多字符串需要连接时,使用StringBuilder是非常有用的,这是因为编译器本质上将其转换为:
string a = b + c + d + e + f;

转换为

r = String.Concat(new String[5] { a, b, c, d, e });

因此,隐式地存在着数组创建的开销。

我建议阅读Eric Lippert撰写的关于C#字符串连接的以下内容:
http://ericlippert.com/2013/06/17/string-concatenation-behind-the-scenes-part-one/ http://ericlippert.com/2013/06/24/string-concatenation-behind-the-scenes-part-two/


我不知道这一点,谢谢。但是通过查看Concat重载函数,只有在一次连接4个以上的字符串时才会发生这种情况。 - Serve Laurijssen

1

我实际上建立并运行了几个与此相关的测试。要查看测试结果,请跳至底部。我使用了以下基准测试方法:

public static string BenchmarkMethod(Action method, int iterations)
{
    var watch = new Stopwatch();
    var results = new List<TimeSpan>(iterations);
    for (int iteration = 0; iteration < iterations; iteration++)
    {
    watch.Start();
    method();
    watch.Stop();
    results.Add(watch.Elapsed);
    watch.Reset();
    }

    var builder = new StringBuilder();
    builder.Append("Method benchmarked: ");
    builder.Append(method.Method.ReflectedType);
    builder.Append(".");
    builder.AppendLine(method.Method.Name);
    builder.Append("Average time in ticks: ");
    builder.AppendLine(results.Average(t => t.Ticks).ToString());

    return builder.ToString();
}

我写了几个像这样的小方法:

public static void StringConcatOperatorX8()
{
    var foo = strings[0] + strings[1] + strings[2] + strings[3] + strings[4] + strings[5] + strings[6] + strings[7] + strings[8];
}

并且:

public static void StringBuilderAppendsX8()
{
    var builder = new StringBuilder();
    builder.Append(strings[0]);
    builder.Append(strings[1]);
    builder.Append(strings[2]);
    builder.Append(strings[3]);
    builder.Append(strings[4]);
    builder.Append(strings[5]);
    builder.Append(strings[6]);
    builder.Append(strings[7]);
    builder.Append(strings[8]);

    var result = builder.ToString();
}

其中strings是一个包含9个30个字母字符串的字符串数组。

它们的连接/附加范围从1到8。我最初写了从1到6,使用3个字母字符串,并取了10,000个样本。

更新:我已经获得了更多的样本(确切地说是100万个),并将更多的字母添加到字符串中。显然,使用StringBuilder会浪费大量性能。在使用StringBuilder时,使用+运算符所需的时间是它的两倍...现在测试需要几秒钟才能完成,我想我应该退出这个主题了。

最终更新:这也非常重要。当你在不同的行上进行连接时,使用+运算符和StringBuilder的差异就出现了。这种方法实际上比使用StringBuilder还要慢:

public static void StringConcatAltOperatorX8()
{
    var foo = strings[0];
    foo += strings[1];
    foo += strings[2];
    foo += strings[3];
    foo += strings[4];
    foo += strings[5];
    foo += strings[6];
    foo += strings[7];
    foo += strings[8];
}

如果每个字符串包含30个字符,共有100万个样本,在同一调用中将所有字符串合并成一个字符串大约需要5.809297个滴答声。将所有字符串分别合并在不同行中大约需要12.933227个滴答声。使用StringBuilder需要11.27558个滴答声。对于回复的长度,我很抱歉,这是我自己需要查证的事情。


正确,这是 StringBuilder 的唯一用例,当需要在多行或循环中进行连接时。否则,您只需使用二进制加法运算符即可。 - Cody Gray

0
请注意,`StringBuilder`是可变的,不像`String`,它专门设计用于以这种方式构建字符串。虽然使用大字符串进行字符串连接可能会对GC产生明显的压力,但如果您使用`StringBuilder`,通常不会成为问题。
如果内存消耗是一个问题(尤其是对于大字符串),并且您知道最终结果的大致长度,您可以使用StringBuilder(int)构造函数来建议一个起始大小,并在构建器增长时最小化内存重新分配。
还要注意,您可以使用`AppendFormat`将变量插入到常量字符串中,例如:
result.Append("{=").Append(field.Name).Append("={");

变成:

result.AppendFormat("{{={0}={{", field.Name);

这将产生完全相同的结果,主要是出于个人偏好而非性能考虑。

最后,如果result整个字符串(而不是正在构建的较大字符串的一部分),只需使用String.Format("{{={0}={{", field.Name)String.Concat("{=", field.Name. "={")而不是StringBuilder。正如MSDN所指出的那样:“尽管StringBuilder类通常比String类提供更好的性能,但您不应该在想要操作字符串时自动将String替换为StringBuilder。性能取决于字符串的大小、为新字符串分配的内存量、您的应用程序执行的系统以及操作类型。您应该准备测试您的应用程序,以确定StringBuilder是否实际上提供了显着的性能改进。”


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接