什么时候使用StringBuilder?

Shi*_*iji 70 c# stringbuilder

我理解StringBuilder的好处.

但是如果我想连接2个字符串,那么我认为没有StringBuilder它会更好(更快).它是否正确?

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

Ale*_*ini 72

我热烈建议你阅读杰夫阿特伍德的微观优化剧场悲剧悲剧.

它将Simple Concatenation与StringBuilder与其他方法相比较.

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

  • 然而,你的阅读是错误的:在很多情况下,当没有涉及循环时无关紧要,在其他情况下它可能很重要很多 (7认同)
  • 并且只是为了表明它有多重要,从你所引用的文章:"在大多数垃圾收集语言中,字符串是不可变的:当你添加两个字符串时,两者的内容都被复制.当你继续在这个循环中添加结果每次都会分配越来越多的内存.这直接导致了糟糕的quadradic n2性能" (2认同)
  • 为什么这是公认的答案。我认为简单地删除链接并说“去读这个”并不是一个好的答案 (2认同)

Pet*_*ter 40

但是如果我想要连接2个字符串,那么我认为没有StringBuilder它会更好(更快).它是否正确?

这确实是正确的,你可以找到为什么完全解释得很好:

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

总结一下:如果你可以一次性结合字符串

var result = a + " " + b  + " " + c + ..
Run Code Online (Sandbox Code Playgroud)

如果没有StringBuilder,你最好只在复制时(最终计算得到的字符串的长度.);

像结构一样

var result = a;
result  += " ";
result  += b;
result  += " ";
result  += c;
..
Run Code Online (Sandbox Code Playgroud)

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

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

经验法则

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

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

  • 当你可以(可读地)指定需要在一个语句中连接的所有内容时,绝对使用连接运算符.(如果要连接的数组,请考虑显式调用String.Concat - 如果需要分隔符,请考虑String.Join.)

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

  • 如果您需要连接的中间结果,而不是提供下一次连接迭代,StringBuilder不会帮助您.例如,如果你从名字和姓氏建立一个全名,然后在最后添加第三条信息(昵称,也许),你只会受益于使用StringBuilder,如果你不这样做需要(名字+姓氏)字符串用于其他目的(正如我们在创建Person对象的示例中所做的那样).

  • 如果你只是有几个串连的事,和你真正想要做他们单独的语句,它并没有真正不管你走哪条路.哪种方法更有效,将取决于级联串的大小参与的数量,和他们连接在一起的顺序.如果你真的相信这段代码是一个性能瓶颈,配置文件或基准两者兼得.


Vic*_*aci 11

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

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


Bob*_*bby 8

不是......如果你连接字符串或者你有许多连接,你应该使用StringBuilder ,就像在循环中一样.

  • 大字符串绝对没有性能优势 - *仅*具有许多连接. (3认同)
  • @Alex:总是不是这样吗?;)不,说真的,我总是在循环中使用StringBuilder进行连接...但是,我的循环都有超过1k次的迭代... @Binary:通常,应该编译为`string s ="abcd" `,至少那是我听到的最后一件事......但是,变量很可能是Concat. (2认同)

Luk*_*keH 5

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

  • 如果在循环中连接,请始终使用StringBuilder.
  • 如果字符串很大,请始终使用StringBuilder.
  • 如果串联代码在屏幕上整洁且可读,那么可能没问题。
    如果不是,请使用StringBuilder.


Rus*_*een 5

转述

然后你数到三,不多也不少。三是你要数的数,数的数是三。四你不要数,你也不要数二,除非你继续到三。一旦达到第三个数字,即第三个数字,然后向你挥舞你的安条克圣手手榴弹

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


adr*_*fek 5

  • 如果在循环中串联字符串,则应考虑使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

  • 30'000次迭代

    • 字符串-2000毫秒
    • StringBuilder-4毫秒
  • 1000次迭代

    • 字符串-2毫秒
    • StringBuilder-1毫秒
  • 500次迭代

    • 字符串-0毫秒
    • StringBuilder-0毫秒


Men*_*nol 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];
    }
}
Run Code Online (Sandbox Code Playgroud)