字符串与 StringBuilder

Posted

技术标签:

【中文标题】字符串与 StringBuilder【英文标题】:String vs. StringBuilder 【发布时间】:2010-09-09 14:10:05 【问题描述】:

我了解StringStringBuilder 之间的区别(StringBuilder 是可变的)但是两者之间的性能差异是否很大?

我正在处理的程序有很多大小写驱动的字符串附加 (500+)。使用StringBuilder 是更好的选择吗?

【问题讨论】:

【参考方案1】:

是的,性能差异很大。请参阅知识库文章“How to improve string concatenation performance in Visual C#”。

我总是尝试先编写清晰的代码,然后再优化性能。这比反过来做要容易得多!然而,在看到我的应用程序在两者之间的巨大性能差异之后,我现在更仔细地考虑了一下。

幸运的是,对您的代码运行性能分析以查看您在哪里花费时间,然后对其进行修改以在需要时使用 StringBuilder 相对简单。

【讨论】:

一个好的经验法则是当你不打算改变它时使用 Strings,如果你要改变它就使用 StringBuilder。 我非常喜欢这个答案,尤其是在性能之前对代码进行清晰的建议。作为开发人员,我们花在阅读代码上的时间与编写代码一样多或更多。 Outlaw:如果我正确理解 ***,我认为这应该成为单独投票的答案。 根据我的经验,如果您尝试连接大约 10-15 个字符串,那么您可以使用字符串,但如果没有。字符串不止于此,然后使用字符串生成器 这仍然取决于一个人如何使用它,作为我喜欢的实际测试的参考Coding Horror - The Sad Tragedy of Micro-Optimization Theater【参考方案2】:

为了澄清 Gillian 所说的关于 4 字符串的内容,如果你有这样的话:

string a,b,c,d;
 a = b + c + d;

那么使用字符串和加号运算符会更快。这是因为(就像 Eric 指出的 Java 一样),它在内部自动使用 StringBuilder(实际上,它使用 StringBuilder 也使用的原语)

但是,如果您正在做的事情更接近:

string a,b,c,d;
 a = a + b;
 a = a + c;
 a = a + d;

然后你需要显式使用 StringBuilder。 .Net 不会在此处自动创建 StringBuilder,因为它毫无意义。在每一行的末尾,“a”必须是一个(不可变的)字符串,因此它必须在每一行上创建和处置一个 StringBuilder。为了速度,您需要使用相同的 StringBuilder 直到您完成构建:

string a,b,c,d;
StringBuilder e = new StringBuilder();
 e.Append(b);
 e.Append(c);
 e.Append(d);
 a = e.ToString();

【讨论】:

C# 编译器没有理由需要将第二个样本与第一个样本区别对待。特别是没有义务在每行的末尾产生一个字符串。编译器可能会按照你说的那样运行,但它没有义务这样做。 @CodesInChaos,一个不可变的字符串变量被专门分配到每一行的末尾,这不是产生产生字符串的义务吗?但是,我同意没有理由对每条线进行不同的处理(我不确定是否这样做),但性能损失来自重新分配,所以没关系。 @SaebAmini - 如果a 是一个局部变量,并且它引用的对象没有被分配给其他变量(可能可以被另一个线程访问),一个好的优化器可以确定在此行序列中,任何其他代码都不会访问a;只有afinal 值很重要。所以它可以把这三行代码当作写成a = b + c + d;来对待。【参考方案3】:

StringBuilder 更可取 IF 您正在执行多个循环,或者在您的代码传递中进行分叉...但是,对于 PURE 性能,如果您可以使用 SINGLE字符串声明,那么它的性能要高得多。

例如:

string myString = "Some stuff" + var1 + " more stuff"
                  + var2 + " other stuff" .... etc... etc...;

更高效
StringBuilder sb = new StringBuilder();
sb.Append("Some Stuff");
sb.Append(var1);
sb.Append(" more stuff");
sb.Append(var2);
sb.Append("other stuff");
// etc.. etc.. etc..

在这种情况下,可以认为 StringBuild 更易于维护,但并不比单个字符串声明更高效。

10 次中有 9 次...使用字符串生成器。

附带说明:string + var 也比在内部使用 StringBuilder 的 string.Format 方法(通常)更高效(当有疑问时...检查反射器!)

【讨论】:

我希望你能说出你是如何知道的/如何验证的。 您不验证反射器的性能:您通过定时发布代码验证性能,使用分析器进行分析并使用反射器寻求解释。 考虑使用 String.Format() 连接少量小字符串,尤其是目标是格式化消息以显示给用户。 这是非常糟糕的信息。 ("string myString is more performance") 根本不是真的。 此答案中的信息不正确。 StringBuilder 比在同一语句中完成的连接要快一点;但是,如果您执行数十万次,您只会注意到两者之间的差异(Source)。正如消息来源所说,“没关系!”【参考方案4】:

一个简单的例子来演示使用String 连接与StringBuilder 时的速度差异:

System.Diagnostics.Stopwatch time = new Stopwatch();
string test = string.Empty;
time.Start();
for (int i = 0; i < 100000; i++)

    test += i;

time.Stop();
System.Console.WriteLine("Using String concatenation: " + time.ElapsedMilliseconds + " milliseconds");

结果:

使用字符串连接:15423 毫秒

StringBuilder test1 = new StringBuilder();
time.Reset();
time.Start();
for (int i = 0; i < 100000; i++)

    test1.Append(i);

time.Stop();
System.Console.WriteLine("Using StringBuilder: " + time.ElapsedMilliseconds + " milliseconds");

结果:

使用 StringBuilder:10 毫秒

因此,第一次迭代耗时 15423 毫秒,而使用 StringBuilder 的第二次迭代耗时 10 毫秒。

在我看来,使用StringBuilder 更快,更快。

【讨论】:

这取决于您要更改字符串的次数。 StringBuilder 有开销,因此对于有限的连接,字符串更快。如果您要追加或修改数千次(通常不是这种情况),那么 StringBuilder 在这些情况下会更快。【参考方案5】:

此基准测试表明,当组合 3 个或更少的字符串时,常规连接速度更快。

http://www.chinhdo.com/20070224/stringbuilder-is-not-always-faster/

StringBuilder 可以显着提高内存使用率,尤其是在将 500 个字符串加在一起的情况下。

考虑以下示例:

string buffer = "The numbers are: ";
for( int i = 0; i < 5; i++)

    buffer += i.ToString();

return buffer;

内存中发生了什么?将创建以下字符串:

1 - "The numbers are: "
2 - "0"
3 - "The numbers are: 0"
4 - "1"
5 - "The numbers are: 01"
6 - "2"
7 - "The numbers are: 012"
8 - "3"
9 - "The numbers are: 0123"
10 - "4"
11 - "The numbers are: 01234"
12 - "5"
13 - "The numbers are: 012345"

通过将这五个数字添加到字符串的末尾,我们创建了 13 个字符串对象!其中12个没用!哇!

StringBuilder 解决了这个问题。它不是我们经常听到的“可变字符串”(.NET 中的所有字符串都是不可变的)。它的工作原理是保留一个内部缓冲区,一个 char 数组。调用 Append() 或 AppendLine() 将字符串添加到 char 数组末尾的空白处;如果数组太小,它会创建一个新的更大的数组,并将缓冲区复制到那里。所以在上面的例子中,StringBuilder 可能只需要一个数组来包含字符串的所有 5 个添加 - 取决于其缓冲区的大小。您可以在构造函数中告诉 StringBuilder 它的缓冲区应该有多大。

【讨论】:

次要:说“我们创建了 13 个字符串对象,其中 12 个没有用”有点奇怪,然后说 StringBuilder 解决了这个问题。毕竟,六个字符串你别无选择,只能创建;他们来自i.ToString()。所以使用 StringBuilder,你仍然需要创建 6 + 1 个字符串;它将创建 13 个字符串减少为创建 7 个字符串。但这仍然是错误的看待它的方式。六个数字字符串的创建不相关。底线:你根本不应该提到i.ToString() 创建的六个字符串;它们不属于效率比较的一部分。【参考方案6】:

字符串与字符串生成器:

首先你必须知道这两个类在哪个程序集中?

所以,

string 存在于 System 命名空间中。

StringBuilder 存在于System.Text 命名空间中。

对于字符串声明:

您必须包含 System 命名空间。 像这样的东西。 Using System;

对于StringBuilder声明:

您必须包含 System.text 命名空间。 像这样的东西。 Using System.text;

现在是真正的问题。

stringStringBuilder有什么区别?

这两者的主要区别在于:

字符串是不可变的。

StringBuilder 是可变的。

那么现在让我们来讨论一下immutablemutable的区别

可变的::表示可变的。

不可变::表示不可改变。

例如:

using System;

namespace StringVsStrigBuilder

    class Program
    
        static void Main(string[] args)
        
            // String Example

            string name = "Rehan";
            name = name + "Shah";
            name = name + "RS";
            name = name + "---";
            name = name + "I love to write programs.";

            // Now when I run this program this output will be look like this.
            // output : "Rehan Shah RS --- I love to write programs."
        
    

所以在这种情况下,我们将更改同一个对象 5 次。

所以显而易见的问题是!当我们将相同的字符串更改 5 次时,实际上会发生什么。

当我们将相同的字符串更改 5 次时会发生这种情况。

让我们看图。

说明:

当我们第一次将这个变量“name”初始化为“Rehan”时,即string name = "Rehan" 此变量在堆栈“名称”上创建并指向该“Rehan”值。 执行此行后:“name = name + “Shah”。引用变量不再指向该对象“Rehan”,它现在指向“Shah”,依此类推。

所以string 是不可变的,这意味着一旦我们在内存中创建了对象,我们就无法更改它们。

因此,当我们连接name 变量时,前一个对象仍保留在内存中,并创建了另一个新的字符串对象...

所以从上图中我们有五个对象,四个对象被扔掉了,它们根本没有被使用。它们仍然保留在内存中,并且占用了内存量。 “垃圾收集器”负责清理内存中的资源。

所以在字符串的情况下,当我们一遍又一遍地操作字符串时,我们有很多对象 Created ans 留在内存中。

这就是字符串变量的故事。

现在让我们看看 StringBuilder 对象。 例如:

using System;
using System.Text;

namespace StringVsStrigBuilder

    class Program
    
        static void Main(string[] args)
        
            // StringBuilder Example

            StringBuilder name = new StringBuilder();
            name.Append("Rehan");
            name.Append("Shah");
            name.Append("RS");
            name.Append("---");
            name.Append("I love to write programs.");


            // Now when I run this program this output will be look like this.
            // output : "Rehan Shah Rs --- I love to write programs."
        
    

所以在这种情况下,我们将更改同一个对象 5 次。

所以显而易见的问题是!当我们将同一个 StringBuilder 更改 5 次时,实际上会发生什么。

当我们将同一个 StringBuilder 更改 5 次时,会发生这种情况。

让我们看图。

说明: 在 StringBuilder 对象的情况下。你不会得到新的对象。同一个对象会在内存中发生变化,因此即使您更改对象等 10,000 次,我们仍然只有一个 stringBuilder 对象。

您没有很多垃圾对象或非引用的 stringBuilder 对象,因为它可以更改。它是可变的,意味着它会随着时间而改变?

区别:

String 存在于 System 命名空间中,其中 Stringbuilder 存在 在 System.Text 命名空间中。 字符串是不可变的,而 StringBuilder 是可变的。

【讨论】:

【参考方案7】:

是的,StringBuilder 在对字符串执行重复操作时提供了更好的性能。这是因为所有更改都是针对单个实例进行的,因此可以节省大量时间,而不是像String那样创建新实例。

字符串与字符串生成器

String

    System 命名空间下 不可变(只读)实例 当值发生连续变化时性能会下降 线程安全

StringBuilder(可变字符串)

    System.Text 命名空间下 可变实例 显示更好的性能,因为对现有实例进行了新的更改

强烈推荐dotnet mob 文章:String Vs StringBuilder in C#。

相关堆栈溢出问题:Mutability of string when string doesn't change in C#?。

【讨论】:

【参考方案8】:

StringBuilder 以使用额外内存为代价减少了分配和赋值的次数。使用得当,完全不需要编译器一遍又一遍地分配越来越大的字符串,直到找到结果。

string result = "";
for(int i = 0; i != N; ++i)

   result = result + i.ToString();   // allocates a new string, then assigns it to result, which gets repeated N times

对比

String result;
StringBuilder sb = new StringBuilder(10000);   // create a buffer of 10k
for(int i = 0; i != N; ++i)

   sb.Append(i.ToString());          // fill the buffer, resizing if it overflows the buffer


result = sb.ToString();   // assigns once

【讨论】:

【参考方案9】:

String 或 StringBuilder 对象的连接操作的性能取决于内存分配发生的频率。 String 连接操作总是分配内存,而 StringBuilder 连接操作仅在 StringBuilder 对象缓冲区太小而无法容纳新数据时才分配内存。因此,如果串联固定数量的 String 对象,则 String 类更适合串联操作。在这种情况下,编译器甚至可以将各个串联操作组合成单个操作。如果串联任意数量的字符串,则 StringBuilder 对象更适合串联操作;例如,如果一个循环连接随机数量的用户输入字符串。

来源:MSDN

【讨论】:

【参考方案10】:

考虑“The Sad Tragedy of Micro-Optimization Theater”。

【讨论】:

【参考方案11】:

StringBuilder 更适合从许多非常量值构建字符串。

如果您从大量常量值(例如 html 或 XML 文档或其他文本块中的多行值)构建字符串,则只需附加到同一个字符串即可,因为几乎所有编译器都会进行“常量折叠”,这是一个在您进行大量常量操作时减少解析树的过程(当您编写类似int minutesPerYear = 24 * 365 * 60 的内容时也会使用它)。对于相互附加非常量值的简单情况,.NET 编译器会将您的代码缩减为类似于 StringBuilder 所做的代码。

但是当编译器无法将附加内容简化为更简单的内容时,您将需要StringBuilder。正如 fizch 指出的那样,这更有可能发生在循环内部。

【讨论】:

【参考方案12】:

除了前面的答案,当我想到这样的问题时,我总是做的第一件事就是创建一个小型测试应用程序。在这个应用程序中,对这两种情况进行一些计时测试,看看哪个更快。

恕我直言,附加 500 多个字符串条目绝对应该使用 StringBuilder。

【讨论】:

【参考方案13】:

如果您需要将超过 4 个字符串附加在一起,我相信 StringBuilder 会更快。此外,它还可以做一些很酷的事情,比如 AppendLine。

【讨论】:

【参考方案14】:

在 .NET 中,StringBuilder 仍然比附加字符串快。我很确定在 Java 中,当您附加字符串时,它们只是在后台创建一个 StringBuffer,因此并没有真正的区别。我不确定为什么他们还没有在 .NET 中这样做。

【讨论】:

【参考方案15】:

StringBuilder 的效率明显更高,但除非您进行大量字符串修改,否则您不会看到这种性能。

下面是一小段代码,用于给出性能示例。正如您所看到的,当您进行大规模迭代时,您实际上才开始看到性能大幅提升。

如您所见,200,000 次迭代耗时 22 秒,而使用 StringBuilder 的 100 万次迭代几乎是瞬间完成的。

string s = string.Empty;
StringBuilder sb = new StringBuilder();

Console.WriteLine("Beginning String + at " + DateTime.Now.ToString());

for (int i = 0; i <= 50000; i++)

    s = s + 'A';


Console.WriteLine("Finished String + at " + DateTime.Now.ToString());
Console.WriteLine();

Console.WriteLine("Beginning String + at " + DateTime.Now.ToString());

for (int i = 0; i <= 200000; i++)

    s = s + 'A';


Console.WriteLine("Finished String + at " + DateTime.Now.ToString());
Console.WriteLine();
Console.WriteLine("Beginning Sb append at " + DateTime.Now.ToString());

for (int i = 0; i <= 1000000; i++)

    sb.Append("A");

Console.WriteLine("Finished Sb append at " + DateTime.Now.ToString());

Console.ReadLine();

以上代码的结果:

在 28/01/2013 16:55:40 开始字符串 +。

在 28/01/2013 16:55:40 完成字符串 +。

在 28/01/2013 16:55:40 开始字符串 +。

在 28/01/2013 16:56:02 完成字符串 +。

在 28/01/2013 16:56:02 开始 Sb 追加。

在 28/01/2013 16:56:02 完成 Sb 追加。

【讨论】:

如果 String builder 如此强大,那么为什么我们有 String 存在。我们为什么不彻底清除 String。我问了这个问题,以便您可以告诉我 String 优于 StringBuilder 的好处 线程安全、内存减少和函数式(如函数式编程)【参考方案16】:

使用字符串进行连接可能会导致运行时复杂度约为O(n^2)

如果您使用StringBuilder,则需要进行的内存复制要少得多。使用StringBuilder(int capacity),如果您可以估计最终String 的大小,您可以提高性能。即使您不精确,您也可能只需将StringBuilder 的容量增加几次,这也有助于提高性能。

【讨论】:

【参考方案17】:

在将StringBuilder 用于任何字符串存储之前,我已经看到在StringBuilder 实例上使用EnsureCapacity(int capacity) 方法调用可显着提高性能。我通常在实例化后的代码行中调用它。它与像这样实例化StringBuilder 具有相同的效果:

var sb = new StringBuilder(int capacity);

此调用会提前分配所需的内存,这会导致在多次Append() 操作期间分配的内存更少。您必须对需要多少内存做出有根据的猜测,但对于大多数应用程序来说,这应该不会太难。我通常会因为内存过多而犯错(我们说的是 1k 左右)。

【讨论】:

StringBuilder 实例化之后不需要调用EnsureCapacity。只需像这样实例化StringBuildervar sb = new StringBuilder(int capacity) 有人告诉我,使用质数会有所帮助。【参考方案18】:

如果您要进行大量字符串连接,请使用 StringBuilder。与字符串连接时,每次都会创建一个新字符串,占用更多内存。

亚历克斯

【讨论】:

【参考方案19】:

String 和 StringBuilder 实际上都是不可变的,StringBuilder 内置了缓冲区,可以更有效地管理其大小。当 StringBuilder 需要调整大小时,就是在堆上重新分配它的时候。默认为 16 个字符,可以在构造函数中设置。

例如。

StringBuilder sb = new StringBuilder(50);

【讨论】:

不确定您是否理解不可变的含义。字符串是不可变的,不能更改。任何“更改”实际上只是旧值保留在堆上,没有任何指针来定位它。 StringBuilder (mutable) 是 heap 上的引用类型,指向堆的指针发生了变化,并为此变化分配了空间。【参考方案20】:

字符串连接会花费更多。 在 Java 中,您可以根据需要使用 StringBuffer 或 StringBuilder。 如果您想要一个同步且线程安全的实现,请选择 StringBuffer。这将比字符串连接更快。

如果您不需要同步或线程安全的实现,请选择 StringBuilder。 这将比字符串连接更快,也比 StringBuffer 更快,因为它们没有同步开销。

【讨论】:

【参考方案21】:

我的方法一直是在连接 4 个或更多字符串时使用 StringBuilder 要么 当我不知道如何进行连接时。

Good performance related article on it here

【讨论】:

【参考方案22】:

从内存的角度来看,StringBuilder 的性能会更好。至于处理,执行时间上的差异可以忽略不计。

【讨论】:

【参考方案23】:

StringBuilder 可能更可取。原因是它分配了比当前需要更多的空间(您设置了字符数),以便为将来的追加留出空间。然后那些适合当前缓冲区的未来追加不需要任何内存分配或垃圾收集,这可能很昂贵。一般情况下,我使用StringBuilder进行复杂的字符串拼接或多重格式化,等数据完成后再转换成普通的String,我又想要一个不可变的对象。

【讨论】:

【参考方案24】:

作为一般经验法则,如果我必须多次设置字符串的值,或者如果有任何附加到字符串,那么它需要是一个字符串生成器。在了解字符串构建器之前,我已经看过我过去编写的应用程序,这些应用程序具有巨大的内存足迹,而且似乎还在不断增长。将这些程序更改为使用字符串生成器会显着减少内存使用量。现在我以字符串生成器发誓。

【讨论】:

以上是关于字符串与 StringBuilder的主要内容,如果未能解决你的问题,请参考以下文章

Json对象与Json字符串的转化JSON字符串与Java对象的转换

C 语言字符串操作 ( strlen 与 sizeof 函数 | 计算 字符串长度 与 内存块大小 )

数据结构与算法|移动字符串与旋转字符串

将 require('...') 与变量一起使用与在 webpack 中使用字符串

第6章 数组指针与字符串指针与函数

(文末送书)字符数组与字符串