我了解StringStringBuilder之间的区别(StringBuilder是可变的),但两者之间是否存在较大的性能差异?

我正在开发的程序有很多大小写驱动的字符串追加(500+)。使用StringBuilder是更好的选择吗?

#1 楼

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

我一直试图首先进行编码以提高清晰度,然后再对其进行优化以提高性能。这比其他方法要容易得多!但是,在看到我的应用程序在两者之间存在巨大的性能差异之后,我现在要更加仔细地考虑它。

幸运的是,在代码上运行性能分析以查看您在哪里花费时间,然后在需要时对其进行修改以使用StringBuilder相对简单。

评论


一个好的经验法则是,在不打算更改它时使用Strings,而在要更改它时使用StringBuilder。

– Tim
08-09-16 at 17:23

我非常喜欢这个答案,尤其是在性能提高之前进行编码的建议。作为开发人员,我们花费的时间与编写代码的时间相同或更多。

–斯科特·劳伦斯(Scott Lawrence)
08/09/16在18:18

Outlaw:如果我正确理解StackOverflow,我认为这应该成为单独投票的答案。

–杰伊·巴祖兹(Jay Bazuzi)
08年9月17日在5:47

根据我的经验,如果您尝试连接10-15左右的弦,则可以使用弦,但是如果没有。字符串多于字符串生成器

– Peeyush
2012年3月1日在6:38

它仍然取决于人们如何使用它,作为实际测试的参考,我喜欢编码恐怖-微型优化剧院的悲伤悲剧

– Erik Philips
13年7月27日在17:38



#2 楼

为了弄清楚吉利安所说的关于4字符串的说法,如果您有这样的话:

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


那么使用字符串和加号运算符会更快。这是因为(就像Eric指出的Java一样),它在内部自动使用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();


评论


C#编译器没有理由需要将第二个样本与第一个样本区别对待。特别是,没有义务在每一行的末尾产生字符串。编译器的行为可能与您所说的一样,但没有义务这样做。

– CodesInChaos
13年7月27日在20:23



@CodesInChaos是在每行的末尾专门分配给一个不可变的字符串变量,这不是产生字符串的义务吗?但是,我同意这样的观点,即没有理由以不同的方式对待每条单独的生产线(并且我不确定是否这样做),但是性能损失来自重新分配,所以这无关紧要。

– Saeb Amini
15年1月22日在12:11

@SaebAmini-如果a是局部变量,并且其引用的对象尚未分配给其他变量(其他线程可能可以访问),那么好的优化程序可以确定a在此期间未被其他任何代码访问行的顺序;只有一件事情的最终价值。因此,它可以将这三行代码视为被写为a = b + c + d;。

–ToolmakerSteve
18-11-15在13:01



#3 楼

如果您要执行多个循环或在代码传递中进行派生,则StringBuilder是更可取的。但是,为了实现PURE性能,如果您可以放弃使用SINGLE字符串声明,那么性能会更高。

例如:

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..


更出色在这种情况下,可以认为StringBuild更可维护,但性能不比单个字符串声明高。

10次中有9次...使用字符串生成器。

附带说明:string + var也比在内部使用StringBuilder的string.Format方法(通常)具有更高的性能(如有疑问,请检查反射器!)

评论


希望您能说出您如何知道/如何验证。

– ChristW
09年7月17日在1:39

您无需验证反射器的性能:您可以通过定时发布代码验证性能,使用探查器进行分析,并使用反射器寻求解释。

– Albin Sunnanbo
10-10-26在6:24

考虑使用String.Format()连接少量的小字符串,尤其是目标是格式化消息以向用户显示。

–加里·金德尔(Gary Kindel)
2010-12-18 15:39

这是非常糟糕的信息。 (“字符串myString性能更高”)根本不正确。

–汤姆·斯蒂克(Tom Stickel)
13年4月4日在4:03



此答案中的信息不正确。 StringBuilder比在同一条语句中进行串联要快一些。但是,只有进行数十万次后,您才会注意到两者之间的差异(来源)。正如消息来源所说,“没关系!”

– David Sherret
2014年10月7日17:57



#4 楼

一个简单的示例演示使用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的速度更快,快得多。

#5 楼

该基准测试表明,合并3个或更少的字符串时,常规连接会更快。

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它的缓冲区在构造函数中应该有多大。

评论


次要问题:说“我们创建了13个字符串对象,其中12个是无用的”,然后说StringBuilder解决了此问题,这有点奇怪。毕竟,您只能选择创建六个字符串。它们来自i.ToString()。因此,使用StringBuilder,您仍然必须创建6 + 1字符串。它将创建13个字符串减少为创建7个字符串。但这仍然是错误的观察方式。六个数字字符串的创建无关紧要。底线:您根本不应该提到i.ToString()创建的六个字符串;它们不是效率比较的一部分。

–ToolmakerSteve
18-11-15在13:17



#6 楼

字符串与字符串生成器:

第一件事,您必须知道这两个类在哪个程序集中运行?

因此,

string存在于System名称空间。



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

对于字符串声明:

您必须包括System名称空间。
类似这样的东西。
Using System;



对于StringBuilder声明:

您必须包括System.text命名空间。
类似的东西。
Using System.text;

现在出现实际问题。

string和StringBuilder之间的区别是什么?

两者的主要区别在于:

字符串是不可变的。




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次时发生的情况。如图所示。



解释:

当我们第一次将此变量“ name”初始化为“ Rehan”时,即string name = "Rehan"
此变量在堆栈“名称”上创建并指向该“ Rehan”值。
执行此行之后:“名称=名称+“ Shah”。引用变量不再指向该对象“ Rehan”现在它指向“ Shah”,依此类推。

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

所以当我们将name变量隐藏起来,将先前的对象保留在内存中,并创建另一个新的字符串对象...

因此,根据上图,我们有五个对象,四个对象被丢弃,它们根本没有使用。它们仍然保留在内存中,并且占用内存量。
“垃圾收集器”对此负责,因此请清理内存中的资源。

因此,如果我们在任何时候操作字符串,一遍又一遍的字符串,我们在内存中创建了许多对象。

这就是字符串Variable的故事。

现在让我们来看一下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次。

因此,显而易见的问题是!当我们更改相同的StringBuilder 5次时,实际上发生了什么。

这是我们更改相同的StringBuilder 5次时发生的情况。

如图所示。


说明:
对于StringBuilder对象。您不会得到新对象。同一对象将在内存中发生更改,因此即使您更改该对象(例如说10,000次),我们仍然只有一个stringBuilder对象。

您没有很多垃圾对象或未引用的stringBuilder对象,因为为什么可以改变。

差异:


System命名空间中存在字符串,而System.Text中Stringbuilder出现在此变量中名称空间。在StringBuilder是mutabe的情况下,string是不可变的。


#7 楼

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

字符串与Stringbuilder



String


System命名空间下
不可变(只读)实例
连续发生值更改时性能下降
线程安全



StringBuilder(可变字符串)


System.Text命名空间下
可变实例
显示出更好的性能由于对现有实例进行了新更改


强烈推荐dotnet mob文章:C#中的String VS StringBuilder。

相关堆栈溢出问题:当字符串
在C#中不变时,字符串的可变性??


#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
}


vs.

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


#9 楼


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


源:MSDN

#10 楼

StringBuilder更适合根据许多非恒定值构建字符串。

如果要根据许多恒定值构建字符串,例如HTML或XML文档中的多行值或其他文本块,只需附加到相同的字符串就可以避免,因为几乎所有编译器都进行“常量折叠”,这是在进行一堆常量操作时减少解析树的过程(在编写时也会使用类似于int minutesPerYear = 24 * 365 * 60)。对于带有非常数值彼此附加的简单情况,.NET编译器会将您的代码缩减为类似于StringBuilder所做的事情。

但是当您的附加无法简化为更简单的事物时通过编译器,您需要一个StringBuilder。正如fizch指出的那样,这更有可能在循环内发生。

#11 楼

我相信,如果您需要将4个以上的字符串附加在一起,则StringBuilder会更快。另外,它还可以做一些很棒的事情,例如AppendLine。

#12 楼

在.NET中,StringBuilder仍然比附加字符串快。我很确定在Java中,当您添加字符串时,它们只是在幕后创建了一个StringBuffer,因此并没有什么区别。我不确定为什么他们还没有在.NET中做到这一点。

#13 楼

考虑一下“微型优化剧院的悲剧”。

#14 楼

使用字符串进行串联会导致运行时复杂性达到O(n^2)的级别。

如果使用StringBuilder,则需要执行的内存复制要少得多。使用StringBuilder(int capacity),如果可以估计最终的String的大小,则可以提高性能。即使您不够精确,也可能只需将StringBuilder的容量增加几次即可,这也有助于提高性能。

#15 楼

我已经看到在将EnsureCapacity(int capacity)实例用于任何字符串存储之前,对StringBuilder实例使用StringBuilder方法调用可显着提高性能。我通常在实例化之后在代码行中调用它。它的效果与您实例化Append()的效果相同:

var sb = new StringBuilder(int capacity);


此调用提前分配了所需的内存,这导致在多次q4312079q操作期间较少的内存分配。您必须对需要多少内存做出有根据的猜测,但是对于大多数应用程序来说,这应该不太困难。我通常会在内存过多方面犯错(我们正在谈论1k左右)。

评论


在StringBuilder实例化之后,无需调用确保容量。只需像这样实例化StringBuilder:var sb = new StringBuilder(int capacity)。

– David FerenczyRogožan
16 Mar 16 '16 at 17:16

有人告诉我使用素数会有所帮助。

–卡尔Gjertsen
16年5月9日在8:46

#16 楼

除了前面的答案,在想到这样的问题时,我总是做的第一件事就是创建一个小型测试应用程序。在此应用程序内,针对这两种情况执行一些时序测试,然后自己查看,这样更快。

恕我直言,附加500个以上的字符串条目肯定应该使用StringBuilder。

#17 楼

实际上,String和StringBuilder都是不可变的,StringBuilder内置了缓冲区,可以更有效地管理其大小。当StringBuilder需要调整大小时,就是在堆上重新分配它的时间。默认情况下,它的大小为16个字符,您可以在构造函数中进行设置。

例如。

StringBuilder sb = new StringBuilder(50);

评论


不确定您了解什么是不变的。字符串是不可变的,不能更改。实际上,任何“更改”只是旧值保留在堆上,而没有任何指针可以定位它。 StringBuilder(可变)是堆上的引用类型,指向堆的指针已更改,并且为此更改分配了空间。

–汤姆·斯蒂克(Tom Stickel)
2014年6月9日22:32

#18 楼

字符串连接将花费更多。
在Java中,您可以根据需要使用StringBuffer或StringBuilder。
如果您想要同步且线程安全的实现,请使用StringBuffer。这将比String串联快。

如果不需要同步或线程安全实现,请使用StringBuilder。
这将比String串联快,也比StringBuffer快。没有同步开销。

#19 楼

StringBuilder可能更可取。原因是它分配的空间超出了当前所需的空间(您设置了字符数),为将来的追加留出了空间。然后,那些适合当前缓冲区的将来的追加不需要任何内存分配或垃圾回收,这可能会很昂贵。通常,我使用StringBuilder进行复杂的字符串合并或多种格式,然后在数据完成后将其转换为普通的String,并且我又想要一个不可变的对象。

#20 楼

如果您要进行大量的字符串连接,请使用StringBuilder。与String串联时,每次都会创建一个新的String,这会占用更多的内存。

Alex

#21 楼

作为一般经验法则,如果我必须多次设置字符串的值,或者该字符串有任何附加值,则它必须是一个字符串生成器。在了解字符串构建器之前,我曾经看过我写过的应用程序,这些构建器具有巨大的内存足迹,而且似乎还在不断增长。更改这些程序以使用字符串生成器可以显着减少内存使用。现在,我向字符串生成器发誓。

#22 楼

我的方法一直是在连接4个或更多字符串时使用StringBuilder

当我不知道如何进行连接时。

在这里

#23 楼

StringBuilder的效率显着提高,但是除非进行大量的字符串修改,否则您将看不到该性能。
下面是快速的代码块,以提供性能示例。如您所见,当您进行大型迭代时,您实际上才真正开始看到性能的大幅提升。
您可以看到200,000次迭代耗时22秒,而使用StringBuilder进行的100万次迭代几乎是即时的。 >
string s = string.Empty;
StringBuilder sb = new StringBuilder();

Console.WriteLine("Beginning String + at " + DateTime.Now.ToString());

for (int i = 0; i <= 50000; i++)
{
    s = s + 'A';
}

Console.WriteLine("Finished String + at " + DateTime.Now.ToString());
Console.WriteLine();

Console.WriteLine("Beginning String + at " + DateTime.Now.ToString());

for (int i = 0; i <= 200000; i++)
{
    s = s + 'A';
}

Console.WriteLine("Finished String + at " + DateTime.Now.ToString());
Console.WriteLine();
Console.WriteLine("Beginning Sb append at " + DateTime.Now.ToString());

for (int i = 0; i <= 1000000; i++)
{
    sb.Append("A");
}
Console.WriteLine("Finished Sb append at " + DateTime.Now.ToString());

Console.ReadLine();

以上代码的结果:

开头的字符串+于28/01/2013 16:55:40。
完成的字符串+于28/01/2013 16: 55:40。
字符串开始+在28/01/2013 16:55:40。
字符串完成+在28/01/2013 16:56:02。
开始Sb附加在28/01/2013 16:56:02。
完成的Sb附加于28/01/2013 16:56:02。

评论


如果字符串构建器是如此强大,那么为什么我们要有字符串存在。我们为什么不彻底清除String。我问了这个问题,以便您可以通过StringBuilder告诉我String的好处

–坚不可摧
18年5月24日在18:55

线程安全性,内存减少和功能(如功能编程中一样)

–feyd
11月10日15:08

#24 楼

从内存的角度来看,StringBuilder的性能会更好。至于处理,执行时间的差异可以忽略不计。