我了解String和StringBuilder(StringBuilder是可变的)之间的区别,但是两者之间是否存在较大的性能差异?
我正在研究的程序有很多大小写驱动的字符串追加(500+)。 使用StringBuilder是更好的选择吗?
是的,性能差异很大。请参见知识库文章"如何在Visual C#中改善字符串连接性能"。
我一直试图首先为清晰起见编写代码,然后再为性能进行优化。这比其他方法要容易得多!但是,在看到我的应用程序在两者之间存在巨大的性能差异之后,我现在要更加仔细地考虑它。
幸运的是,在代码上运行性能分析以查看花费的时间,然后在需要的地方将其修改为使用StringBuilder相对简单。
为了澄清吉利安所说的关于4弦的话,如果您有这样的话:
1 2
| string a,b,c,d;
a = b + c + d; |
那么使用字符串和加号运算符会更快。这是因为(就像Eric所指出的Java一样),它在内部自动使用StringBuilder(实际上,它使用StringBuilder也使用的原语)
但是,如果您正在做的事情更接近:
1 2 3 4
| string a,b,c,d;
a = a + b;
a = a + c;
a = a + d; |
然后,您需要显式使用StringBuilder。 .Net不会在这里自动创建StringBuilder,因为它毫无意义。在每一行的末尾," a"必须是一个(不可变的)字符串,因此它必须在每一行上创建并放置一个StringBuilder。为了提高速度,您需要使用相同的StringBuilder直到完成构建:
1 2 3 4 5 6
| string a,b,c,d;
StringBuilder e = new StringBuilder();
e.Append(b);
e.Append(c);
e.Append(d);
a = e.ToString(); |
如果您要执行多个循环或在代码传递中进行派生,则StringBuilder是更可取的...但是,为了实现PURE性能,如果您可以放弃使用SINGLE字符串声明,那么性能会更高。
例如:
1 2
| string myString ="Some stuff" + var1 +" more stuff"
+ var2 +" other stuff" .... etc... etc...; |
比
1 2 3 4 5 6 7
| 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更具可维护性,但性能不比单个字符串声明高。
十分之九(...)使用字符串生成器。
附带说明一下:string + var也比在内部使用StringBuilder的string.Format方法(通常)具有更高的性能(如果有疑问,请检查反射器!)
此基准表明,合并3个或更少的字符串时,常规串联速度更快。
StringBuilder is not always faster – Part 1 of 2
StringBuilder可以显着提高内存使用率,尤其是在将500个字符串加在一起的情况下。
考虑以下示例:
1 2 3 4 5 6
| string buffer ="The numbers are:";
for( int i = 0; i < 5; i++)
{
buffer += i.ToString();
}
return buffer; |
内存中会发生什么?将创建以下字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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它的缓冲区在构造函数中应该有多大。
一个简单的示例演示使用String串联与StringBuilder时速度的差异:
1 2 3 4 5 6 7 8 9
| 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"); |
结果:
Using String concatenation: 15423 milliseconds
1 2 3 4 5 6 7 8 9
| 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"); |
结果:
Using StringBuilder: 10 milliseconds
结果,第一次迭代花费了15423毫秒,而使用StringBuilder的第二次迭代花费了10毫秒。
在我看来,使用StringBuilder更快,快得多。
是的,StringBuilder在对字符串执行重复操作时可提供更好的性能。这是因为所有更改都是针对单个实例进行的,因此可以节省大量时间,而不必创建像String这样的新实例。
字符串与Stringbuilder
-
<5233>
-
在System名称空间下
-
不变(只读)实例
-
不断发生价值变化时,性能会下降
-
线程安全
-
StringBuilder(可变字符串)
-
在System.Text名称空间下
-
可变实例
-
由于对现有实例进行了新更改,因此显示了更好的性能
Strongly recommend dotnet mob article : String Vs StringBuilder in C#.
Related Stack Overflow question: Mutability of string when string
doesn't change in C#?.
字符串与字符串生成器:
首先,您必须知道这两个类在哪个组件中生活?
所以,
System名称空间中存在字符串。
和
StringBuilder存在于System.Text名称空间中。
对于字符串声明:
您必须包含System命名空间。
这样的事情。
Using System;
和
对于StringBuilder声明:
您必须包括System.Text命名空间。
这样的事情。
Using System.text;
现在来实际的问题。
字符串和StringBuilder之间的区别是什么?
两者之间的主要区别在于:
字符串是不可变的。
和
StringBuilder是可变的。
所以现在让我们讨论不可变和可变之间的区别
可变的::表示可变的。
不可变::表示不可更改。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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"时,i-e string name ="Rehan"
该变量在堆栈"名称"上创建并指向该" Rehan"值。
执行此行后:" name = name +" Shah"。引用变量不再指向该对象" Rehan",现在指向" Shah",依此类推。
因此string是不可变的,这意味着一旦在内存中创建对象,就无法更改它们。
因此,当我们隐含name变量时,先前的对象保留在内存中,并创建了另一个新的字符串对象...
因此,根据上图,我们有五个对象,四个对象被扔掉了,根本没有使用。它们仍然保留在内存中,并且占用内存量。
"垃圾收集器"对此负责,因此请清除内存中的资源。
因此,在任何情况下,当我们一遍又一遍地操作字符串时,都会有许多对象创建并保留在内存中。
这就是字符串Variable的故事。
现在,让我们看一下StringBuilder对象。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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对象,因为为什么可以更改它。它是可变的,意味着它会随时间变化吗?
区别:
-
字符串存在于系统名称空间中,而Stringbuilder存在
在System.Text命名空间中。
-
在StringBuilder是mutabe的地方,string是不可变的。
StringBuilder减少了分配和分配的数量,但使用了额外的内存。正确使用它可以完全避免编译器一遍又一遍地分配越来越大的字符串,直到找到结果为止。
1 2 3 4 5
| 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
} |
与
1 2 3 4 5 6 7 8
| 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 |
The performance of a concatenation operation for a String or StringBuilder object depends on how often a memory allocation occurs. A String concatenation operation always allocates memory, whereas a StringBuilder concatenation operation only allocates memory if the StringBuilder object buffer is too small to accommodate the new data. Consequently, the String class is preferable for a concatenation operation if a fixed number of String objects are concatenated. In that case, the individual concatenation operations might even be combined into a single operation by the compiler. A StringBuilder object is preferable for a concatenation operation if an arbitrary number of strings are concatenated; for example, if a loop concatenates a random number of strings of user input.
资料来源:MSDN
StringBuilder更适合根据许多非常数值构建字符串。
如果要从许多常量值中构建字符串,例如HTML或XML文档中的多行值或其他文本块,则只需附加到同一字符串即可,因为几乎所有编译器都可以这样做"常量折叠",这是在您进行一堆常量操作时减少解析树的过程(当您编写类似int minutesPerYear = 24 * 365 * 60之类的内容时,也会使用它)。对于带有非恒定值的简单情况,.NET编译器会将您的代码缩减为类似于StringBuilder的内容。
但是,当编译器无法将您的附加内容简化为更简单的内容时,您将需要StringBuilder。正如fizch指出的那样,这更有可能在循环内发生。
考虑"微型优化剧院的悲剧"。
我相信,如果您需要将4个以上的字符串附加在一起,则StringBuilder会更快。另外,它还可以做一些很棒的事情,例如AppendLine。
在.NET中,StringBuilder仍然比附加字符串快。我敢肯定,在Java中,当您添加字符串时,它们只是在幕后创建了一个StringBuffer,所以并没有什么真正的区别。我不确定为什么他们还没有在.NET中做到这一点。
在将EnsureCapacity(int capacity)方法用于任何字符串存储之前,在StringBuilder实例上使用EnsureCapacity(int capacity)方法调用,我已经看到了显着的性能提升。我通常在实例化之后在代码行中调用它。它具有与实例化StringBuilder相同的效果:
1
| var sb = new StringBuilder(int capacity); |
该调用会提前分配所需的内存,从而在多次Append()操作期间导致较少的内存分配。您必须对需要多少内存做出有根据的猜测,但是对于大多数应用程序来说,这应该不太困难。我通常会在内存过多方面犯错(我们正在谈论1k左右)。
使用字符串进行串联会导致运行时复杂度约为O(n^2)。
如果使用StringBuilder,则需要执行的内存复制要少得多。使用StringBuilder(int capacity),如果可以估计最终的String的大小,则可以提高性能。即使您不够精确,也可能只需将StringBuilder的容量增加几次,这也可以帮助提高性能。
字符串连接将花费更多。
在Java中,您可以根据需要使用StringBuffer或StringBuilder。
如果您想要同步且线程安全的实现,请使用StringBuffer。这将比String串联更快。
如果您不需要同步或线程安全的实现,请使用StringBuilder。
这将比String串联更快,也比StringBuffer更快,因为它们没有同步开销。
实际上,String和StringBuilder都是不可变的,StringBuilder内置了缓冲区,可以更有效地管理其大小。当StringBuilder需要调整大小时,就是在堆上重新分配它的时间。默认情况下,它的大小为16个字符,您可以在构造函数中进行设置。
例如。
StringBuilder sb =新的StringBuilder(50);
除了前面的答案,在想到这样的问题时,我总是做的第一件事就是创建一个小型测试应用程序。在此应用程序中,针对这两种情况执行一些时序测试,然后亲自查看哪个更快。
恕我直言,追加500多个字符串条目绝对应该使用StringBuilder。
StringBuilder可能更可取。原因是它分配的空间超出了当前所需的空间(您设置了字符数),为将来的追加留出了空间。然后,那些适合当前缓冲区的将来的追加不需要任何内存分配或垃圾回收,这可能会很昂贵。通常,我使用StringBuilder进行复杂的字符串连接或多种格式,然后在数据完成后将其转换为普通的String,并且我再次想要一个不可变的对象。
如果您要进行大量的字符串连接,请使用StringBuilder。与字符串连接时,每次都会创建一个新的字符串,从而消耗更多的内存。
亚历克斯
我的方法一直是串联4个或更多字符串时使用StringBuilder
要么
当我不知道如何进行级联时。
此处有关性能良好的文章
StringBuilder的效率明显更高,但是除非进行大量的字符串修改,否则您将看不到该性能。
下面是快速的代码块,以给出性能示例。如您所见,当您进行大型迭代时,您实际上才真正开始看到性能的大幅提升。
如您所见,200,000次迭代耗时22秒,而使用StringBuilder进行的100万次迭代几乎是即时的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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(); |
以上代码的结果:
Beginning String + at 28/01/2013 16:55:40.
Finished String + at 28/01/2013 16:55:40.
Beginning String + at 28/01/2013 16:55:40.
Finished String + at 28/01/2013 16:56:02.
Beginning Sb append at 28/01/2013 16:56:02.
Finished Sb append at 28/01/2013 16:56:02.
作为一般经验法则,如果我必须多次设置字符串的值,或者该字符串有任何追加,则它必须是一个字符串生成器。在了解字符串构建器之前,我曾经看过我写过的应用程序,这些构建器具有巨大的内存足迹,而且似乎还在不断增长。更改这些程序以使用字符串生成器可以显着减少内存使用。现在,我向字符串生成器宣誓。
从内存的角度来看,StringBuilder的性能会更好。至于处理,执行时间的差异可以忽略不计。