89

我了解 StringBuilder 的好处。

但是如果我想连接 2 个字符串,那么我认为在没有 StringBuilder 的情况下这样做会更好(更快)。它是否正确?

在什么时候(字符串数量)使用 StringBuilder 会变得更好?

4

12 回答 12

83

我强烈建议您阅读Jeff Atwood的《微优化剧院的悲惨悲剧》 。

它处理简单连接与 StringBuilder 与其他方法。

现在,如果您想查看一些数字和图表,请点击链接;)

于 2009-12-01T12:12:26.170 回答
49

但是,如果我想连接 2 个字符串,那么我认为在没有 StringBuilder 的情况下这样做会更好(更快)。它是否正确?

这确实是正确的,您可以在以下内容中找到解释得很好的原因:

http://www.yoda.arachsys.com/csharp/stringbuilder.html

总结:如果您可以一次性连接字符串,例如

var result = a + " " + b  + " " + c + ..

没有 StringBuilder 你会更好,因为只有在复制时(结果字符串的长度是预先计算的。);

对于像这样的结构

var result = a;
result  += " ";
result  += b;
result  += " ";
result  += c;
..

每次都会创建新对象,因此您应该考虑使用 StringBuilder。

文章最后总结了这些经验法则:

经验法则

那么,什么时候应该使用StringBuilder,什么时候应该使用字符串连接运算符呢?

  • 当您在一个重要的循环中连接时,绝对使用 StringBuilder - 特别是如果您不确定(在编译时)您将通过循环进行多少次迭代。例如,一次读取文件一个字符,使用 += 运算符构建一个字符串可能会导致性能自杀。

  • 当您可以(可读)指定需要在一个语句中连接的所有内容时,请务必使用连接运算符。(如果您有一系列要连接的东西,请考虑显式调用 String.Concat - 或 String.Join 如果您需要分隔符。)

  • 不要害怕将文字分成几个连接的位 - 结果是一样的。例如,您可以通过将长文字分成几行来提高可读性,而不会损害性能。

  • 如果您需要连接的中间结果而不是提供下一次连接迭代,StringBuilder 不会帮助您。例如,如果您从名字和姓氏构建一个全名,然后在末尾添加第三条信息(可能是昵称),那么只有在您不这样做的情况下,您才会从使用 StringBuilder 中受益需要 (first name + last name) 字符串用于其他目的(就像我们在创建 Person 对象的示例中所做的那样)。

  • 如果您只需要进行一些串联,并且您真的想在单独的语句中执行它们,那么您走哪条路并不重要。哪种方式更有效将取决于所涉及的字符串大小的连接数量以及它们连接的顺序。如果您真的认为那段代码是性能瓶颈,请以两种方式对其进行分析或基准测试。

于 2009-12-01T12:12:43.610 回答
16

System.String 是一个不可变的对象——这意味着每当你修改它的内容时,它都会分配一个新的字符串,这需要时间(和内存?)。使用 StringBuilder 您可以修改对象的实际内容,而无需分配新内容。

因此,当您需要对字符串进行许多修改时,请使用 StringBuilder。

于 2009-12-01T12:12:48.883 回答
9

不是真的......如果你连接字符串或者你有很多连接,你应该使用 StringBuilder ,比如在一个循环中。

于 2009-12-01T12:11:02.880 回答
6
  • 如果在循环中连接字符串,则应考虑使用 StringBuilder 而不是常规 String
  • 如果是单一连接,您可能根本看不到执行时间的差异

这是一个简单的测试应用程序来证明这一点:

class Program
{
    static void Main(string[] args)
    {
        const int testLength = 30000;
        var StartTime = DateTime.Now;

        //TEST 1 - String
        StartTime = DateTime.Now;
        String tString = "test string";
        for (int i = 0; i < testLength; i++)
        {
            tString += i.ToString();
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 2000 ms

        //TEST 2 - StringBuilder
        StartTime = DateTime.Now;
        StringBuilder tSB = new StringBuilder("test string");
        for (int i = 0; i < testLength; i++)
        {
            tSB.Append(i.ToString());
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 4 ms

        Console.ReadLine();
    }
}

结果:

  • 30'000 次迭代

    • 字符串 - 2000 毫秒
    • 字符串生成器 - 4 毫秒
  • 1000 次迭代

    • 字符串 - 2 毫秒
    • 字符串生成器 - 1 毫秒
  • 500 次迭代

    • 字符串 - 0 毫秒
    • 字符串生成器 - 0 毫秒
于 2016-09-19T16:19:18.923 回答
5

转述

然后你数到三,不多也不少。三是你要数的数,数的数是三。你不能算四,也不能算二,除非你接着算到三。一旦达到第三个数字,即第三个数字,那么你就向安提阿的神圣手榴弹投掷

我通常将字符串生成器用于任何会导致三个或更多字符串串联的代码块。

于 2009-12-01T12:22:39.173 回答
5

由于很难找到一个既不受意见影响也不受骄傲之战的解释,我想自己在 LINQpad 上编写一些代码来测试这一点。

我发现使用小字符串而不是使用 i.ToString() 会改变响应时间(在小循环中可见)。

该测试使用不同的迭代序列来将时间测量值保持在可比较的范围内。

我将在最后复制代码,以便您自己尝试(results.Charts...Dump()在 LINQPad 之外无法使用)。

输出(X 轴:测试的迭代次数,Y 轴:滴答时间):

迭代顺序:2、3、4、5、6、7、8、9、10 迭代顺序:2、3、4、5、6、7、8、9、10

迭代顺序:10、20、30、40、50、60、70、80 迭代顺序:10、20、30、40、50、60、70、80

迭代顺序:100、200、300、400、500 迭代顺序:100、200、300、400、500

代码(使用 LINQPad 5 编写):

void Main()
{
    Test(2, 3, 4, 5, 6, 7, 8, 9, 10);
    Test(10, 20, 30, 40, 50, 60, 70, 80);
    Test(100, 200, 300, 400, 500);
}

void Test(params int[] iterationsCounts)
{
    $"Iterations sequence: {string.Join(", ", iterationsCounts)}".Dump();
    
    int testStringLength = 10;
    RandomStringGenerator.Setup(testStringLength);
    var sw = new System.Diagnostics.Stopwatch();
    var results = new Dictionary<int, TimeSpan[]>();
        
    // This call before starting to measure time removes initial overhead from first measurement
    RandomStringGenerator.GetRandomString(); 
        
    foreach (var iterationsCount in iterationsCounts)
    {
        TimeSpan elapsedForString, elapsedForSb;
        
        // string
        sw.Restart();
        var str = string.Empty;

        for (int i = 0; i < iterationsCount; i++)
        {
            str += RandomStringGenerator.GetRandomString();
        }
        
        sw.Stop();
        elapsedForString = sw.Elapsed;


        // string builder
        sw.Restart();
        var sb = new StringBuilder(string.Empty);

        for (int i = 0; i < iterationsCount; i++)
        {
            sb.Append(RandomStringGenerator.GetRandomString());
        }
        
        sw.Stop();
        elapsedForSb = sw.Elapsed;

        results.Add(iterationsCount, new TimeSpan[] { elapsedForString, elapsedForSb });
    }


    // Results
    results.Chart(r => r.Key)
    .AddYSeries(r => r.Value[0].Ticks, LINQPad.Util.SeriesType.Line, "String")
    .AddYSeries(r => r.Value[1].Ticks, LINQPad.Util.SeriesType.Line, "String Builder")
    .DumpInline();
}

static class RandomStringGenerator
{
    static Random r;
    static string[] strings;
    
    public static void Setup(int testStringLength)
    {
        r = new Random(DateTime.Now.Millisecond);
        
        strings = new string[10];
        for (int i = 0; i < strings.Length; i++)
        {
            strings[i] = Guid.NewGuid().ToString().Substring(0, testStringLength);
        }
    }
    
    public static string GetRandomString()
    {
        var indx = r.Next(0, strings.Length);
        return strings[indx];
    }
}
于 2019-10-21T11:38:24.167 回答
4

但是如果我想连接 2 个字符串,那么我认为在没有 StringBuilder 的情况下这样做会更好更快。它是否正确?

是的。但更重要的是,在这种情况下使用 vanilla更具可读性。String另一方面,在循环中使用它是有意义的,并且也可以像连接一样可读。

我会警惕引用特定数量的串联作为阈值的经验法则。在循环中使用它(并且仅在循环中)可能同样有用,更容易记住并且更有意义。

于 2009-12-01T12:11:33.800 回答
4

没有确定的答案,只有经验法则。我自己的个人规则是这样的:

  • 如果在循环中连接,请始终使用StringBuilder.
  • 如果字符串很大,请始终使用StringBuilder.
  • 如果连接代码在屏幕上整洁且可读,那么它可能没问题。
    如果不是,请使用StringBuilder.
于 2009-12-01T12:16:24.663 回答
3

只要您可以实际输入串联的数量(a + b + c ...),它就不会有太大的不同。N 平方(在 N = 10 时)是 100 倍的减速,这应该不会太糟糕。

最大的问题是当您连接数百个字符串时。在 N=100 时,你会得到 10000 倍的减速。这很糟糕。

于 2009-12-01T12:21:34.757 回答
2

我认为在何时使用或何时不使用之间没有明确的界限。当然,除非有人进行了一些广泛的测试以得出黄金条件。

对我来说,如果只是连接 2 个大字符串,我不会使用 StringBuilder。如果有不确定计数的循环,我很可能会这样做,即使循环可能是小计数。

于 2009-12-01T12:17:02.090 回答
1

单个连接不值得使用 StringBuilder。我通常使用 5 个连接作为经验法则。

于 2009-12-01T12:10:35.250 回答