我了解String
和StringBuilder
(StringBuilder
可变)之间的区别,但两者之间是否存在很大的性能差异?
我正在处理的程序有很多大小写驱动的字符串附加(500+)。是使用StringBuilder
更好的选择吗?
我了解String
和StringBuilder
(StringBuilder
可变)之间的区别,但两者之间是否存在很大的性能差异?
我正在处理的程序有很多大小写驱动的字符串附加(500+)。是使用StringBuilder
更好的选择吗?
是的,性能差异很大。请参阅知识库文章“如何提高 Visual C# 中的字符串连接性能”。
我总是尝试先编写清晰的代码,然后再优化性能。这比反过来做要容易得多!然而,在看到我的应用程序在两者之间的巨大性能差异之后,我现在更仔细地考虑了一下。
幸运的是,对代码运行性能分析以查看您在哪里花费时间,然后StringBuilder
在需要的地方对其进行修改以使用它是相对简单的。
为了澄清 Gillian 关于 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();
如果您正在执行多个循环或代码传递中的分叉,则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 方法(通常)更高效(当有疑问时......检查反射器!)
一个简单的例子来演示使用String
串联 vs时的速度差异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
更快,更快。
这个基准测试表明,当组合 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 它的缓冲区应该有多大。
字符串与字符串生成器:
首先你必须知道这两个类生活在哪个程序集中?
所以,
字符串存在于System
命名空间中。
和
StringBuilder存在于System.Text
命名空间中。
对于字符串声明:
您必须包含System
命名空间。像这样的东西。
Using System;
和
对于StringBuilder声明:
您必须包含System.text
命名空间。像这样的东西。
Using System.text;
现在来实际的问题。
string和StringBuilder有什么区别?
这两者之间的主要区别在于:
字符串是不可变的。
和
StringBuilder是可变的。
所以现在让我们讨论一下不可变和可变之间的区别
Mutable: : 表示可变的。
Immutable: : 表示不可更改。
例如:
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"
这个变量在堆栈“name”上创建并指向那个“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 次。
所以显而易见的问题是!当我们将相同的 StringBuilder 更改 5 次时,实际上会发生什么。
这就是我们将相同的 StringBuilder 更改 5 次时发生的情况。
说明: 如果是 StringBuilder 对象。你不会得到新的对象。同一个对象将在内存中发生变化,因此即使您更改对象等说 10,000 次,我们仍然只有一个 stringBuilder 对象。
您没有很多垃圾对象或 non_referenced stringBuilder 对象,因为它可以更改。它是可变的,意味着它会随着时间而改变?
差异:
是的,StringBuilder
在对字符串执行重复操作时提供更好的性能。这是因为所有更改都是针对单个实例进行的,因此可以节省大量时间,而不是像String
.
String
System
命名空间下StringBuilder
(可变字符串)
System.Text
命名空间下强烈推荐 dotnet mob 文章:String Vs StringBuilder in C#。
相关的堆栈溢出问题:当字符串在 C# 中不变时字符串的可变性?.
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
String 或 StringBuilder 对象的连接操作的性能取决于内存分配发生的频率。String 连接操作总是分配内存,而 StringBuilder 连接操作仅在 StringBuilder 对象缓冲区太小而无法容纳新数据时才分配内存。因此,如果串联固定数量的 String 对象,则 String 类更适合串联操作。在这种情况下,编译器甚至可以将各个连接操作组合成一个操作。如果串联任意数量的字符串,则 StringBuilder 对象更适合串联操作;例如,如果一个循环连接随机数量的用户输入字符串。
资料来源:MSDN
StringBuilder
更适合从许多非常量值构建字符串。
如果您从大量常量值(例如 HTML 或 XML 文档或其他文本块中的多行值)构建字符串,则只需附加到同一个字符串即可,因为几乎所有编译器都这样做“常量折叠”,当你有一堆常量操作时减少解析树的过程(当你写类似的东西时也会使用它int minutesPerYear = 24 * 365 * 60
)。对于相互附加非常量值的简单情况,.NET 编译器会将您的代码简化为类似于所做的事情StringBuilder
。
但是,当编译器无法将您的附加内容简化为更简单的内容时,您将需要一个StringBuilder
. 正如 fizch 指出的那样,这更有可能发生在循环内部。
考虑“微优化剧院的悲惨悲剧”。
除了前面的答案,当我想到这样的问题时,我总是做的第一件事就是创建一个小型测试应用程序。在这个应用程序中,对这两种情况执行一些计时测试,然后自己看看哪个更快。
恕我直言,附加 500 多个字符串条目绝对应该使用 StringBuilder。
我相信如果您需要将超过 4 个字符串附加在一起,StringBuilder 会更快。另外它可以做一些很酷的事情,比如 AppendLine。
在 .NET 中,StringBuilder 仍然比附加字符串快。我很确定在 Java 中,当您附加字符串时,它们只是在后台创建一个 StringBuffer ,所以并没有真正的区别。我不确定他们为什么还没有在.NET 中这样做。
使用字符串进行连接可能会导致运行时复杂度为O(n^2)
.
如果您使用 a StringBuilder
,则必须完成的内存复制要少得多。StringBuilder(int capacity)
如果您可以估计决赛的规模,您可以提高性能String
。即使您不精确,您也可能只需将容量增加StringBuilder
几倍,这也有助于提高性能。
在将其用于任何字符串存储之前,我已经看到EnsureCapacity(int capacity)
在实例上使用方法调用带来了显着的性能提升。StringBuilder
我通常在实例化后的代码行中调用它。它的效果与您StringBuilder
像这样实例化一样:
var sb = new StringBuilder(int capacity);
此调用会提前分配所需的内存,这会导致在多个Append()
操作期间分配的内存更少。您必须对需要多少内存做出有根据的猜测,但对于大多数应用程序来说,这应该不会太难。我通常会因为内存过多而犯错(我们说的是 1k 左右)。
StringBuilder
效率要高得多,但除非您进行大量字符串修改,否则您不会看到这种性能。
下面是一个快速的代码块,给出了一个性能示例。正如您所看到的,当您进行大型迭代时,您实际上才开始看到主要的性能提升。
如您所见,200,000 次迭代耗时 22 秒,而使用 100 万次迭代StringBuilder
几乎是即时的。
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。
从 28/01/2013 16:56:02 开始附加 Sb。
在 28/01/2013 16:56:02 完成 Sb 追加。
如果您要进行大量字符串连接,请使用 StringBuilder。与字符串连接时,每次都会创建一个新字符串,从而占用更多内存。
亚历克斯
我的方法一直是在连接 4 个或更多字符串时使用 StringBuilder,或者当我不知道如何进行连接时。
String 和 StringBuilder 实际上都是不可变的,StringBuilder 内置了缓冲区,可以更有效地管理其大小。当 StringBuilder 需要调整大小时,就是在堆上重新分配它的时候。默认情况下,它的大小为 16 个字符,您可以在构造函数中设置它。
例如。
StringBuilder sb = new StringBuilder(50);
字符串连接会花费你更多。在 Java 中,您可以根据需要使用 StringBuffer 或 StringBuilder。如果您想要一个同步且线程安全的实现,请选择 StringBuffer。这将比字符串连接更快。
如果您不需要同步或线程安全实现,请选择 StringBuilder。这将比字符串连接更快,也比 StringBuffer 更快,因为它们没有同步开销。
从内存的角度来看,StringBuilder 的性能会更好。至于处理,执行时间的差异可以忽略不计。
StringBuilder 可能更可取。原因是它分配了比当前需要更多的空间(您设置了字符数),以便为将来的追加留出空间。然后那些适合当前缓冲区的未来追加不需要任何内存分配或垃圾收集,这可能很昂贵。一般来说,我使用StringBuilder进行复杂的字符串连接或多重格式化,然后在数据完成后转换为普通的String,我又想要一个不可变的对象。
作为一般的经验法则,如果我必须多次设置字符串的值,或者如果有任何附加到字符串,那么它需要是一个字符串生成器。在了解字符串构建器之前,我已经看过我过去编写的应用程序,这些应用程序具有巨大的内存足迹,而且似乎还在不断增长。将这些程序更改为使用字符串生成器会显着减少内存使用量。现在我以字符串生成器发誓。