与+连接相比,为什么StringBuilder
会变慢?
StringBuilder
旨在避免额外的对象创建,但为什么它会影响性能呢?
static void Main(string[] args)
{
int max = 1000000;
for (int times = 0; times < 5; times++)
{
Console.WriteLine("\ntime: {0}", (times+1).ToString());
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < max; i++)
{
string msg = "Your total is ";
msg += "$500 ";
msg += DateTime.Now;
}
sw.Stop();
Console.WriteLine("String +\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
sw = Stopwatch.StartNew();
for (int j = 0; j < max; j++)
{
StringBuilder msg = new StringBuilder();
msg.Append("Your total is ");
msg.Append("$500 ");
msg.Append(DateTime.Now);
}
sw.Stop();
Console.WriteLine("StringBuilder\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
}
Console.Read();
}
编辑:按建议移出范围变量:
答案 0 :(得分:15)
更改以便不会一直实例化StringBuilder,而是.Clear()
它:
time: 1
String + : 3348ms
StringBuilder : 3151ms
time: 2
String + : 3346ms
StringBuilder : 3050ms
等。
注意,这仍然会测试完全相同的功能,但尝试更智能地重用资源。
代码:(也住在 http://ideone.com/YuaqY )
using System;
using System.Text;
using System.Diagnostics;
public class Program
{
static void Main(string[] args)
{
int max = 1000000;
for (int times = 0; times < 5; times++)
{
{
Console.WriteLine("\ntime: {0}", (times+1).ToString());
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < max; i++)
{
string msg = "Your total is ";
msg += "$500 ";
msg += DateTime.Now;
}
sw.Stop();
Console.WriteLine("String +\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
}
{
Stopwatch sw = Stopwatch.StartNew();
StringBuilder msg = new StringBuilder();
for (int j = 0; j < max; j++)
{
msg.Clear();
msg.Append("Your total is ");
msg.Append("$500 ");
msg.Append(DateTime.Now);
}
sw.Stop();
Console.WriteLine("StringBuilder\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
}
}
Console.Read();
}
}
答案 1 :(得分:6)
每次迭代都会创建StringBuilder
的新实例,这会产生一些开销。既然你没有将它用于实际意图(即:构建大字符串,否则需要很多字符串连接操作),看到性能比连接更差并不奇怪。
StringBuilder
的更常见的比较/用法类似于:
string msg = "";
for (int i = 0; i < max; i++)
{
msg += "Your total is ";
msg += "$500 ";
msg += DateTime.Now;
}
StringBuilder msg_sb = new StringBuilder();
for (int j = 0; j < max; j++)
{
msg_sb.Append("Your total is ");
msg_sb.Append("$500 ");
msg_sb.Append(DateTime.Now);
}
有了这个,你会发现StringBuilder
和连接之间存在显着的性能差异。 “显着”是指orders of magnitude,而不是你在例子中观察到的差异大约为10%。
由于StringBuilder
不需要构建大量可以丢弃的中间字符串,因此可以获得更好的性能。这就是它的意思。对于较小的字符串,为了简单和清晰起见,最好使用字符串连接。
答案 2 :(得分:2)
使用更长的字符串时,StringBuilder的好处应该是显而易见的。
每次连接字符串时都会创建一个新的字符串对象,因此字符串越长,从旧字符串复制到新字符串所需的越多。
此外,创建许多临时对象可能会对StopWatch无法衡量的性能产生负面影响,因为它会污染&#34;污染&#34;带有临时对象的托管堆,可能会导致更多垃圾回收周期。
修改你的测试以创建(更多)更长的字符串并使用(更多)更多的连接/追加操作,并且StringBuilder应该表现得更好。
答案 3 :(得分:2)
除了不以最有效的方式使用StringBuilder
之外,您还没有尽可能有效地使用字符串连接。如果你知道你提前连接了多少个字符串,那么在一行上完成这一切应该是最快的。编译器优化操作,以便不生成中间字符串。
我添加了几个测试用例。一个与sehe建议的基本相同,另一个在一行中生成字符串:
sw = Stopwatch.StartNew();
builder = new StringBuilder();
for (int j = 0; j < max; j++)
{
builder.Clear();
builder.Append("Your total is ");
builder.Append("$500 ");
builder.Append(DateTime.Now);
}
sw.Stop();
Console.WriteLine("StringBuilder (clearing)\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
sw = Stopwatch.StartNew();
for (int i = 0; i < max; i++)
{
msg = "Your total is " + "$500" + DateTime.Now;
}
sw.Stop();
Console.WriteLine("String + (one line)\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
以下是我在机器上看到的输出示例:
time: 1
String + : 3707ms
StringBuilder : 3910ms
StringBuilder (clearing) : 3683ms
String + (one line) : 3645ms
time: 2
String + : 3703ms
StringBuilder : 3926ms
StringBuilder (clearing) : 3666ms
String + (one line) : 3625ms
一般情况下:
- 如果您在很多步骤中构建一个大字符串,或者您不知道将多少个字符串连接在一起,StringBuilder
会做得更好。
- 只要它是一个合理的选项选项,在一个表达式中将它们全部混合在一起就更好了。
答案 4 :(得分:1)
请注意
string msg = "Your total is ";
msg += "$500 ";
msg += DateTime.Now;
编译为
string msg = String.Concat("Your total is ", "$500 ");
msg = String.Concat(msg, DateTime.Now.ToString());
每次迭代总计两个concats和一个ToString。此外,单个String.Concat非常快,因为它知道结果字符串的大小,因此它只分配结果字符串一次,然后快速将源字符串复制到其中。这意味着在实践中
String.Concat(x, y);
总是优于
StringBuilder builder = new StringBuilder();
builder.Append(x);
builder.Append(y);
因为StringBuilder不能使用这样的快捷方式(你可以调用一个附加,或者一个删除,这是String.Concat无法实现的)。
StringBuilder的工作方式是分配一个初始缓冲区并将字符串长度设置为0.对于每个Append,它必须检查缓冲区,可能分配更多的缓冲区空间(通常将旧缓冲区复制到新缓冲区),复制字符串并增加构建器的字符串长度。 String.Concat不需要做所有这些额外的工作。
因此,对于简单的字符串连接,x + y(即String.Concat)将始终优于StringBuilder。
现在,一旦你开始将大量字符串连接到一个缓冲区中,或者你在缓冲区上做了很多操作,你就会开始从StringBuilder中获益,你需要在不需要的时候继续创建新的字符串。使用StringBuilder。这是因为StringBuilder只是偶尔以块的形式分配新的内存,但String.Concat,String.SubString等(几乎)总是分配新的内存。 (类似“”.SubString(0,0)或String.Concat(“”,“”)之类的东西不会分配内存,但这些都是退化的情况。)
答案 5 :(得分:0)
我认为比较String和StringBuilder之间的效率而不是时间更好。
msdn说的是什么: String被称为immutable,因为一旦创建了它就无法修改它的值。看似修改String的方法实际上返回一个包含修改的新String。如果需要修改类似字符串的对象的实际内容,请使用System.Text.StringBuilder类。
string msg = "Your total is "; // a new string object
msg += "$500 "; // a new string object
msg += DateTime.Now; // a new string object
看哪一个更好。
答案 6 :(得分:0)
这是一个示例,演示了StringBuilder
执行速度比字符串连接更快的情况:
static void Main(string[] args)
{
const int sLen = 30, Loops = 10000;
DateTime sTime, eTime;
int i;
string sSource = new String('X', sLen);
string sDest = "";
//
// Time StringBuilder.
//
for (int times = 0; times < 5; times++)
{
sTime = DateTime.Now;
System.Text.StringBuilder sb = new System.Text.StringBuilder((int)(sLen * Loops * 1.1));
Console.WriteLine("Result # " + (times + 1).ToString());
for (i = 0; i < Loops; i++)
{
sb.Append(sSource);
}
sDest = sb.ToString();
eTime = DateTime.Now;
Console.WriteLine("String Builder took :" + (eTime - sTime).TotalSeconds + " seconds.");
//
// Time string concatenation.
//
sTime = DateTime.Now;
for (i = 0; i < Loops; i++)
{
sDest += sSource;
//Console.WriteLine(i);
}
eTime = DateTime.Now;
Console.WriteLine("Concatenation took : " + (eTime - sTime).TotalSeconds + " seconds.");
Console.WriteLine("\n");
}
//
// Make the console window stay open
// so that you can see the results when running from the IDE.
//
}
结果#1 String Builder花了0秒。 连接耗时:8.7659616秒。
结果#2 String Builder花了0秒。 连接耗时:8.7659616秒。
结果#3 String Builder花了0秒。 连接时间为:8.9378432秒。
结果#4 String Builder花了0秒。 连接采取:8.7972128秒。
结果#5 String Builder花了0秒。 连接花了:8.8753408秒。
StringBulder
比+连接快得多..