如何明智地使用 StringBuilder?

74
我对如何使用StringBuilder类有点困惑,首先:
引用如下: “string”对象的连接操作总是从现有的“string”和新数据创建一个新对象。 StringBuilder对象维护一个缓冲区以容纳新数据的连接。 如果有空间,则将新数据附加到缓冲区的末尾; 否则,将分配一个新的、更大的缓冲区,将原始缓冲区中的数据复制到新缓冲区,然后将新数据附加到新缓冲区。
但是,创建StringBuilder实例来避免创建新的String对象的好处在哪里?这听起来像是一对一的交易。
static void Main(string[] args)
{
    String foo = "123";
    using (StringBuilder sb = new StringBuilder(foo)) // also sb isn't disposable, so there will be error
    {
        sb.Append("456");
        foo = sb.ToString();
    }

    Console.WriteLine(foo);
    Console.ReadKey();
}

为什么不应该只使用

+=

编辑:好的,我现在知道如何重复使用一个StringBuilder实例(但我仍然不知道这是否符合代码标准),但是如果只有一个string,这并不值得使用,对吧?


5
http://www.dotnetperls.com/stringbuilder-performance 与 http://www.dotnetperls.com/string-concat 的比较。 - Steve
1
@royB,“string contention uses stringbuilder (str1 + str2) natively”是什么意思?实际上,字符串连接并没有在内部使用StringBuilder。 - Lasse V. Karlsen
3个回答

113

修改像 string 这样的 immutable 结构必须通过复制结构来完成,这将消耗更多内存并减慢应用程序的运行时间(还会增加 GC 时间等)。

StringBuilder 通过使用相同的可变对象来解决此问题。

然而:

当在编译时连接一个 string,如下所示:

string myString = "123";
myString += "234";
myString += "345";

它实际上会编译成类似这样的东西:
string myString = string.Concat("123", "234", "345");

如果函数输入的字符串数量已知,则使用此函数比使用 StringBuilder 更快。

因此,在编译时已知字符串连接的情况下,应该优先选择使用 string.Concat()

至于在以下情况中,字符串数量未知:

string myString = "123";
if (Console.ReadLine() == "a")
{
    myString += "234";
}
myString += "345";

现在编译器无法使用 string.Concat() 函数,然而,只有在连接 6-7 个或更多的 strings 时,StringBuilder 在时间和内存消耗方面才更有效率。
不好的用法:
StringBuilder myString = new StringBuilder("123");
myString.Append("234");
myString.Append("345");

良好的实践用法(请注意使用if):
StringBuilder myString = new StringBuilder("123");
if (Console.ReadLine() == "a")
{
    myString.Append("234");
}
myString.Append("345");

最佳实践用法(注意使用 while 循环):
StringBuilder myString = new StringBuilder("123");
while (Console.ReadLine() == "a")
{
    myString.Append("234"); //Average loop times 4~ or more
}
myString.Append("345");

你最后的两个例子不是完全一样吗? - Jaimie Knox
2
@JaimieKnox 注意 while/if 和注释。 - Tamir Vered
4
谢谢。我可能已经读了100遍但还是错过了它。 - Jaimie Knox
如果您在for循环中将列表中的每个项目进行拼接呢? += 会编译为 string.concatenate(a,b,...,z) 吗?如果是这样,那么在列表中有 ~4个或更多项目时,是否使用 StringBuilder - monster
1
@monster 编译器无法对List中的所有strings进行假设,因此不会将其编译为string.Concat(一次性连接所有strings)。因此,如果您假设将进行多个连接操作,则应使用StringBuilder - Tamir Vered

13

String 是一个不可变的类。你不能修改它,只能创建新的 strings

所以当你写下 result += a; 时,在这一点上内存中有三个独立的 stringsaresult 的旧值和新值。当然,如果你只连接有限数量的 strings,那么这绝对没问题。但如果你在迭代大量集合的 for 循环中执行此操作,可能会成为一个问题。

StringBuilder 类在这些情况下提供了更好的性能。它不是创建新的 strings 来存储连接的结果,而是使用同一个对象。因此,如果你使用 stringBuilder.Append(a);,你永远不会有与 "旧的 result 值" 等效的东西。


当连接少量的 strings 时,这种内存效率当然是要付出代价的。由于有更多的开销与不可变的 string 类相比,StringBuilder 在速度方面通常较慢。


要记住的一件事是,当你需要中间字符串时,StringBuilder 可能会变得不太高效,因为对它调用 .ToString() 会创建一个新的 string 的副本。


3
由于字符串是不可变的,所以在连接字符串时会创建一个新的字符串。因此,当需要连接多个字符串时,就会创建许多对象。虽然每个字符串只使用一次,但这会增加垃圾回收器的额外工作量,对内存消耗不会太大。
而StringBuilder则每次都使用相同的对象,但付出的代价是不太容易使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接