字符串 vs. 字符串构建器

233

我知道StringStringBuilder之间的区别(StringBuilder可以被修改),但是这两者之间有很大的性能差异吗?

我正在开发的程序具有许多基于情况的字符串追加(500+)。使用StringBuilder是更好的选择吗?

24个回答

254

是的,性能差异很大。请参阅KB文章“如何提高Visual C#中字符串连接的性能”。

我一直尝试首先编写清晰易懂的代码,然后再优化性能。这比反过来做容易得多!然而,看到我的应用程序之间的巨大性能差异后,我现在会更加谨慎地考虑它。

幸运的是,相对而言,运行性能分析以查看代码花费时间的位置并进行修改以在需要时使用StringBuilder是相对简单的。


53
一个好的经验法则是,在你不打算改变字符串时使用 Strings,而在你需要改变它时使用 StringBuilder。 - Tim
32
我非常喜欢这个答案,特别是建议先编写易读代码再考虑性能。作为开发人员,我们花费的时间阅读代码与写代码相当甚至更多。 - Scott Lawrence
2
根据我的经验,如果您尝试连接大约10-15个字符串,则可以使用String,但如果字符串数量超过那个范围,则应使用StringBuilder。 - Peeyush
5
这仍然取决于使用方式,对于实际测试的参考,我喜欢《编码恐怖:微优化剧院的悲惨悲剧》这篇文章。 - Erik Philips
1
这里是编程恐怖-Coding Horror《微观优化剧院的悲惨悲剧》的正确链接。@ErikPhilips的链接已经失效,我无法编辑。 - Maddin
显示剩余3条评论

60

为了澄清Gillian关于4字符串的说法,如果你有这样的东西:

string a,b,c,d;
 a = b + c + d;

如果是使用字符串和加号运算符,速度会更快。这是因为它(像Java一样,就像Eric指出的那样)在内部自动使用StringBuilder(实际上,它使用了一个与StringBuilder相同的原语)。

但是,如果你要做的更接近以下情况:

string a,b,c,d;
 a = a + b;
 a = a + c;
 a = a + d;

那么您需要显式地使用 StringBuilder。 .Net 在这里不会自动创建一个 StringBuilder,因为这是没有意义的。在每行结尾处,“a”必须是(不可变的)字符串,因此它必须在每行上创建和处理一个 StringBuilder。为了速度,您需要在构建完成之前一直使用同一个 StringBuilder:

string a,b,c,d;
StringBuilder e = new StringBuilder();
 e.Append(b);
 e.Append(c);
 e.Append(d);
 a = e.ToString();

6
C#编译器没有理由需要以与第一个示例不同的方式处理第二个示例。特别地,没有必要在每行末尾生成字符串。编译器可能会按照你说的那样运作,但它并没有义务这样做。 - CodesInChaos
@CodesInChaos,每行末尾都专门为不可变的字符串变量分配了特定的值,这难道不会产生生成字符串的义务吗?但是,我同意这个观点,即没有理由将每个单独的行区别对待(我不确定是否这样做),性能损失来自重新分配,因此无关紧要。 - Saeb Amini
1
@SaebAmini - 如果a是一个局部变量,并且它所引用的对象没有被分配给其他变量(可能可以被另一个线程访问),那么一个好的优化器可以确定在这些代码行序列中没有其他代码访问a;只有a的最终值才是重要的。因此,它可以将这三行代码视为a = b + c + d;编写的方式。 - ToolmakerSteve

33

如果您的代码需要多次循环或分叉,那么StringBuilder是更可取的选择...但是,就性能而言,如果您可以使用单个字符串声明,那么它会更高效。

例如:

string myString = "Some stuff" + var1 + " more stuff"
                  + var2 + " other stuff" .... etc... etc...;
比...更高效。
StringBuilder sb = new StringBuilder();
sb.Append("Some Stuff");
sb.Append(var1);
sb.Append(" more stuff");
sb.Append(var2);
sb.Append("other stuff");
// etc.. etc.. etc..
在这种情况下,StringBuilder 可以被认为比单个字符串声明更易维护,但不比其更高效。但十之九,使用 StringBuilder 更好。
顺带一提:string + var 的性能也比使用 string.Format 方法更高(通常情况下),后者在内部使用 StringBuilder(当有疑问时... 可以检查反编译器!)。

21
我希望你能说出你是如何得知/如何验证的。 - ChrisW
2
你不应该在反射器中验证性能:你应该通过计时发布代码、使用分析器进行分析并在反射器中寻找解释来验证性能。 - Albin Sunnanbo
3
如果目的是格式化消息以向用户展示,特别是连接少量小字符串,考虑使用String.Format()方法。 - Gary Kindel
1
这是非常糟糕的信息。("string myString is more performant") 完全不正确。 - Tom Stickel
3
这个答案中的信息是不正确的。使用StringBuilder比在同一语句中进行连接略快一些;然而,只有当您这样做数十万次时才会注意到两者之间的差异 (来源). 正如来源中所说,“这并不重要!” - David Sherret
Albin Sunnanbo:你不需要比较运行时间来确定哪个程序更快。你可以仅通过数学计算来实现。假设我们知道每个常量操作的成本C,那么你可以将成本公式化为一个数学表达式。即使你不知道操作的具体成本,只要你能说出操作x所需的时间比操作y长(或者相等),我们就可以制定出最优解。 - kam

31

使用String拼接与StringBuilder比较速度的简单示例:

System.Diagnostics.Stopwatch time = new Stopwatch();
string test = string.Empty;
time.Start();
for (int i = 0; i < 100000; i++)
{
    test += i;
}
time.Stop();
System.Console.WriteLine("Using String concatenation: " + time.ElapsedMilliseconds + " milliseconds");

结果:

使用字符串拼接:15423毫秒

StringBuilder test1 = new StringBuilder();
time.Reset();
time.Start();
for (int i = 0; i < 100000; i++)
{
    test1.Append(i);
}
time.Stop();
System.Console.WriteLine("Using StringBuilder: " + time.ElapsedMilliseconds + " milliseconds");

结果:

使用 StringBuilder: 10 毫秒

因此,第一次迭代花费了15423毫秒,而使用StringBuilder的第二次迭代只花费了10毫秒。

在我看来,使用StringBuilder更快,快了很多。


1
这取决于你要更改字符串的次数。StringBuilder 有开销,因此对于有限的连接,string 更快。如果您要进行数千次附加或修改(通常不是这种情况),则 StringBuilder 在这些场景中更快。 - SendETHToThisAddress

27

这个基准测试显示,当连接三个或更少的字符串时,常规连接比较快。

http://www.chinhdo.com/20070224/stringbuilder-is-not-always-faster/

StringBuilder可以显著提高内存使用效率,特别是在将500个字符串连接在一起的情况下。

考虑以下示例:

string buffer = "The numbers are: ";
for( int i = 0; i < 5; i++)
{
    buffer += i.ToString();
}
return buffer;

内存中会发生什么?以下字符串被创建:

1 - "The numbers are: "
2 - "0"
3 - "The numbers are: 0"
4 - "1"
5 - "The numbers are: 01"
6 - "2"
7 - "The numbers are: 012"
8 - "3"
9 - "The numbers are: 0123"
10 - "4"
11 - "The numbers are: 01234"
12 - "5"
13 - "The numbers are: 012345"

在我们将这五个数字添加到字符串末尾后,我们创建了13个字符串对象!其中12个是无用的!哇!

StringBuilder解决了这个问题。它不是像我们经常听到的那样的“可变字符串”(.NET中的所有字符串都是不可变的)。它通过保持一个内部缓冲区,即char数组来工作。调用Append()或AppendLine()将字符串添加到char数组末尾的空白处;如果数组太小,则创建一个新的、更大的数组,并将缓冲区复制到那里。因此,在上面的例子中,StringBuilder可能只需要一个数组来包含对字符串的所有5个添加——这取决于其缓冲区的大小。您可以在构造函数中告诉StringBuilder它的缓冲区应该有多大。


2
小细节:说“我们创建了13个字符串对象,其中12个是无用的”,然后说StringBuilder解决了这个问题有点奇怪。毕竟,六个字符串你别无选择,它们来自i.ToString()。所以使用 StringBuilder,您仍然必须创建6 + 1个字符串;它将创建13个字符串减少到7个字符串。但这仍然是错误的看法;创建六个数字字符串并不重要。底线:您根本不应该提到由i.ToString()创建的六个字符串;它们不是效率比较的一部分。 - ToolmakerSteve

22

String Vs String Builder:

首先你需要知道这两个类分别在哪个程序集中。

因此,

string 存在于 System 命名空间中。

以及

StringBuilder 存在于 System.Text 命名空间中。

对于 string 的声明:

需要包含 System 命名空间。 类似于这样。 Using System;

以及

对于 StringBuilder 的声明:

需要包含 System.text 命名空间。 类似于这样。 Using System.text;

现在进入实际问题。

什么是 stringStringBuilder 之间的区别?

这两者之间的主要区别是:

string 是不可变的。

以及

StringBuilder 是可变的。

接下来让我们讨论一下 不可变的可变的 的区别。

可变的: 意味着可以更改。

不可变的: 意味着无法更改。

例如:

using System;

namespace StringVsStrigBuilder
{
    class Program
    {
        static void Main(string[] args)
        {
            // String Example

            string name = "Rehan";
            name = name + "Shah";
            name = name + "RS";
            name = name + "---";
            name = name + "I love to write programs.";

            // Now when I run this program this output will be look like this.
            // output : "Rehan Shah RS --- I love to write programs."
        }
    }
}

因此,在这种情况下,我们将更改同一对象5次。

那么显而易见的问题是!当我们5次更改相同的字符串时,在幕后实际发生了什么。

这就是当我们5次更改相同的字符串时发生的事情。

让我们看一下这张图。

enter image description here

解释:

当我们首先将该变量“name”初始化为“Rehan”,即string name = "Rehan",此变量在栈上创建并指向该“Rehan”值。执行此行之后:“name = name + "Shah"。”引用变量不再指向该对象“Rehan”,它现在指向“Shah”等。

因此,string是不可变的,这意味着一旦我们在内存中创建了对象,就无法更改它们。

因此,当我们连接name变量时,以前的对象仍然保留在内存中,并且另一个新的字符串对象被创建...

因此,从上图中我们可以得出五个对象,其中四个对象被抛弃,它们根本没有被使用。它们仍然留在内存中并占用一定的内存。 “垃圾回收器”负责清理内存中的这些资源。

因此,在字符串的情况下,每当我们反复操纵字符串时,我们都会创建许多对象并保留在内存中。

因此,这就是字符串变量的故事。

现在让我们看一下StringBuilder对象。例如:

using System;
using System.Text;

namespace StringVsStrigBuilder
{
    class Program
    {
        static void Main(string[] args)
        {
            // StringBuilder Example

            StringBuilder name = new StringBuilder();
            name.Append("Rehan");
            name.Append("Shah");
            name.Append("RS");
            name.Append("---");
            name.Append("I love to write programs.");


            // Now when I run this program this output will be look like this.
            // output : "Rehan Shah Rs --- I love to write programs."
        }
    }
}

所以在这种情况下,我们将更改同一对象5次。

那么显而易见的问题是!当我们更改同一字符串生成器5次时,在幕后实际发生了什么。

这就是当我们更改同一字符串生成器5次时会发生什么。

让我们看看这张图片。 enter image description here

解释: 对于StringBuilder对象。你不会得到新的对象。同一对象将在内存中进行更改,因此即使您更改对象并说10000次,我们仍将只有一个stringBuilder对象。

您没有大量的垃圾对象或非引用stringBuilder对象,因为它可以被更改。它是可变的,意味着随着时间的推移而更改?

区别:

  • String存在于System命名空间中,而StringBuilder存在于System.Text命名空间中。
  • string是不可变的,而StringBuilder是可变的。

13

是的,StringBuilder在执行重复操作时可以提供更好的性能。这是因为所有的更改都是针对单个实例进行的,因此它可以节省很多时间,而不是像String一样创建一个新实例。

String vs StringBuilder

  • String

    1. 位于System命名空间中
    2. 不可变(只读)实例
    3. 当连续更改值时性能会下降
    4. 线程安全
  • StringBuilder(可变字符串)

    1. 位于System.Text命名空间中
    2. 可变实例
    3. 由于新更改是针对现有实例进行的,因此显示出更好的性能

强烈推荐 dotnet mob 文章:C# 中的 String Vs StringBuilder.

相关的 Stack Overflow 问题:当字符串不改变时字符串的可变性?.


8

StringBuilder可以减少分配和赋值的次数,但会增加额外的内存使用。如果使用得当,它可以完全消除编译器反复分配越来越大的字符串直到找到结果的需要。

string result = "";
for(int i = 0; i != N; ++i)
{
   result = result + i.ToString();   // allocates a new string, then assigns it to result, which gets repeated N times
}

对比。

String result;
StringBuilder sb = new StringBuilder(10000);   // create a buffer of 10k
for(int i = 0; i != N; ++i)
{
   sb.Append(i.ToString());          // fill the buffer, resizing if it overflows the buffer
}

result = sb.ToString();   // assigns once

6
一个 String 或 StringBuilder 对象的连接操作的性能取决于内存分配的频率。String 连接操作总是会分配内存,而 StringBuilder 连接操作仅在 StringBuilder 对象缓冲区无法容纳新数据时才会分配内存。因此,在连接固定数量的 String 对象时,使用 String 类更可取。在这种情况下,编译器甚至可以将单个连接操作合并为单个操作。如果要连接任意数量的字符串,例如循环连接用户输入的随机数量的字符串,则最好使用 StringBuilder 对象。

4
考虑一下“微优化剧院的悲惨悲剧”。此内容与IT技术有关,该文章强调了过度优化代码的不良影响。请注意保留HTML标记。

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