字符串 vs. 字符串构建器

233

我知道StringStringBuilder之间的区别(StringBuilder可以被修改),但是这两者之间有很大的性能差异吗?

我正在开发的程序具有许多基于情况的字符串追加(500+)。使用StringBuilder是更好的选择吗?

24个回答

4

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

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

但是,当您的追加无法被编译器简化为更简单的形式时,您需要一个StringBuilder。正如fizch所指出的那样,这更可能发生在循环内部。


2

在之前的回答基础上,当我面对这种问题时,我总是首先创建一个小型测试应用程序。在该应用程序内,对两种情况进行一些计时测试,亲自查看哪种更快。

我的观点是,追加500多个字符串条目肯定应该使用StringBuilder。


1

StringBuilder 是更高效的,但除非您正在进行大量字符串修改,否则您将看不到该性能。

下面是一个快速的代码块,以说明其性能。正如您所看到的,只有当您进行大量迭代时才会开始看到主要的性能提升。

如您所见,200,000 次迭代花费了 22 秒,而使用 StringBuilder 的 1 百万次迭代几乎瞬间完成。

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();

以上代码的结果:

开始字符串 + 于2013年1月28日16:55:40。

结束字符串 + 于2013年1月28日16:55:40。

开始字符串 + 于2013年1月28日16:55:40。

结束字符串 + 于2013年1月28日16:56:02。

开始Sb追加于2013年1月28日16:56:02。

完成Sb追加于2013年1月28日16:56:02。


1
如果StringBuilder如此强大,那么为什么我们还需要String存在呢?为什么不完全淘汰String呢?我问这个问题是为了让你告诉我String相对于StringBuilder的好处。 - Unbreakable
线程安全、内存减少和函数式(指函数式编程) - feyd

1

我相信如果你需要连接超过4个字符串,StringBuilder会更快。此外,它还可以执行一些很酷的操作,比如AppendLine。


1
在.NET中,StringBuilder仍比字符串拼接更快。我非常确定在Java中,当你拼接字符串时,它们只是在底层创建一个StringBuffer,所以实际上没有什么区别。我不确定为什么他们在.NET中还没有这样做。

1

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

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


1
在编程方面,我发现在使用 StringBuilder 存储字符串之前,调用 EnsureCapacity(int capacity) 方法可以显著提高性能。通常我会在实例化后的代码行上调用它。这与您像这样实例化 StringBuilder 具有相同的效果:
var sb = new StringBuilder(int capacity);

这个调用提前分配所需的内存,在多个 Append() 操作期间减少了更少的内存分配。你必须根据需要做出明智的猜测,但对于大多数应用程序而言,这不应该太困难。我通常会对稍微多一点的内存容错(我们说的是1k左右)。


StringBuilder 实例化后,没有必要立即调用 EnsureCapacity。只需像这样实例化 StringBuildervar sb = new StringBuilder(int capacity) - David Ferenczy Rogožan
我被告知使用质数会有帮助。 - Karl Gjertsen
我认为质数与字典的初始大小相关,而不是与字符串构建器相关,因为字典在幕后使用了哈希表,现在由于哈希函数的细节开始变得重要起来。然而,我认为.NET已经将这个需求抽象化了(与MFC相比),并且在幕后自动将给定的容量转换为质数。 - undefined

0
如果你需要大量字符串拼接,请使用 StringBuilder。当你使用 String 进行拼接时,每次都会创建一个新的 String 对象,从而消耗更多的内存。
Alex

0

0

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

例如:

StringBuilder sb = new StringBuilder(50);


3
不确定您是否理解"immutable"的意思。字符串是不可变的,它们不能被改变。任何"被改变"的操作实际上只是旧值仍然留存在堆内存中,但没有任何指针可以找到它。而可变的 StringBuilder 是一种引用类型在堆上,指向堆内存的指针会发生改变,并且分配空间以进行这种更改。 - Tom Stickel

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