为什么StringBuilder比字符串连接慢?

时间:2011-11-11 00:38:10

标签: c# stringbuilder string-concatenation

与+连接相比,为什么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();
    }

enter image description here

编辑:按建议移出范围变量:

enter image description here

7 个答案:

答案 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比+连接快得多..