何时使用StringBuilder?


82

我了解StringBuilder的好处。

但是,如果我想连接2个字符串,那么我认为不使用StringBuilder这样做会更好(更快)。它是否正确?

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


1
我相信以前已经讨论过了。
Mark Schultheiss


Answers:


79

我热烈建议您阅读杰夫·阿特伍德(Jeff Atwood)的《微优化剧院的悲剧》

它处理简单串联与StringBuilder与其他方法。

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


+1!花时间担心这是花时间不做可能实际上重要的事情。
Greg D

8
但是,您的阅读是错误的:在很多情况下,当不涉及循环时,这并不重要;在其他情况下,则可能很重要
Peter

1
我删除了修改,因为在接受的答案中它只是错误的信息。
彼得

2
只是为了说明它有多重要,在您引用的文章中:“在大多数垃圾回收语言中,字符串是不可变的:当您添加两个字符串时,两个内容都会被复制。在此循环中不断添加结果,每次都会分配越来越多的内存。这直接导致糟糕的Quadradic n2性能”
Peter

1
为什么这是公认的答案。我不认为简单地删除链接并说“去读这篇文章”是一个很好的答案
Kolob Canyon

44

但是,如果我想隐含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的情况下才能受益需要(名字+姓氏)字符串用于其他目的(如在创建Person对象的示例中所做的那样)。

  • 如果您只需要执行几个串联操作,而您真的想在单独的语句中进行操作,那么走哪条路并不重要。哪种方法更有效将取决于所涉及的字符串大小的串联数量以及串联的顺序。如果您真的认为那段代码是性能瓶颈,则可以两种方式对其进行配置或基准测试。


13

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

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


8

并非如此...如果连接大型字符串或有许多串联(例如循环),则应使用StringBuilder 。


1
那是错的。StringBuilder仅当循环或串联是规范的性能问题时,才应使用。
Alex Bagnolini

2
@Alex:并非总是这样吗?;)不,认真的说,我一直在循环中使用StringBuilder进行串联...尽管,我的循环都具有超过1k的迭代... @Binary:通常,应将其编译为string s = "abcd",至少这是最后一件事我听说...尽管有变量,但很有可能是Concat。
鲍比(Bobby)

1
事实是:事实并非总是如此。我一直使用字符串运算符,a + "hello" + "somethingelse"而不必担心。如果这将成为问题,我将使用StringBuilder。但是我一开始并不担心它,而花了更少的时间来编写它。
Alex Bagnolini

3
大型字符串绝对没有性能上的好处-只有许多串联。
康拉德·鲁道夫

1
@Konrad:您确定没有性能优势吗?每次连接大字符串时,都在复制大量数据。每次连接小字符串时,您只复制少量数据。
LukeH

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毫秒
    • StringBuilder-4毫秒
  • 1000次迭代

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

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

5

释义

然后你要数到三,不多也不少。计数的数字为三,计数的数字为三。你不算四,你也不算二,除了你然后前进到三。一旦达到第三位,即第三位,那么您的安提阿圣手榴弹

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


它取决于:并置仅复制一份:“ Russell” +“” + Steen +“。”,仅复制一份,因为它会预先计算字符串的长度。仅当必须拆分连接时,才应开始考虑构建器
Peter

4

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

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

我知道这是一个古老的话题,但是我只知道学习并且想知道您认为什么是“大字符串”?
MatthewD

4

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

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

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


“我会警惕以串联的特定数量作为阈值的经验法则”。同样,在应用常识后,请考虑在6个月内该人员返回您的代码。
Phil Cooper 2014年

3

只要您可以实际键入级联数(a + b + c ...),它就不会有太大的区别。N平方(在N = 10处)是100倍的减速,应该还不错。

最大的问题是当您串联数百个字符串时。在N = 100时,您的速度降低了10000倍。真不好


3

由于很难找到一个既不受观点影响也不引起骄傲之争的解释,所以我想在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];
    }
}

2

我认为何时使用或何时不使用之间没有界限。当然,除非有人进行了广泛的测试以得出最佳条件。

对我来说,仅连接2个巨大的字符串就不会使用StringBuilder。如果存在不确定的循环数,即使循环数很小,我也很可能会这样做。


确实,使用StringBuilder连接2个字符串是完全错误的,但这与perf无关。测试-只是将其用于错误的事情。
马克·格雷韦尔

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.