当我需要连接两个字符串时,我使用 String.Format (如果它发生在代码中的多个位置,则使用 StringBuilder)。
我看到一些优秀的程序员不会关注字符串连接的复杂性,而只是使用“+”运算符。
我知道使用“+”运算符会使应用程序使用更多内存,但是复杂性呢?
当我需要连接两个字符串时,我使用 String.Format (如果它发生在代码中的多个位置,则使用 StringBuilder)。
我看到一些优秀的程序员不会关注字符串连接的复杂性,而只是使用“+”运算符。
我知道使用“+”运算符会使应用程序使用更多内存,但是复杂性呢?
这是一篇关于我们自己的Jeff Atwood在Coding Horror上的不同字符串连接方法的优秀文章:
(来源:codinghorror.com)
这是帖子的要点。
[显示了几种字符串连接方法]
把你发痒的小扳机手指从那个编译键上拿开,想一想。这些方法中的哪一种会更快?
有答案吗?伟大的!
还有..请打鼓..正确答案:
它。只是。没有。事情!
该答案假设您正在谈论运行时复杂性。
Using+
创建了一个新的字符串对象,这意味着两个旧字符串对象的内容都必须复制到新对象中。对于大量的连接,例如在一个紧密的循环中,这可以变成一个 O(n^2) 操作。
作为非正式证明,假设您有以下代码:
string foo = "a";
for(int i = 0; i < 1000; i++)
{
foo += "a";
}
循环的第一次迭代,首先将foo
("a") 的内容复制到一个新的字符串对象中,然后是文字 "a" 的内容。那是两份。第二次迭代有三个副本;两个来自 new foo
,一个来自字面“a”。第 1000 次迭代将有 1001 次复制操作。总份数为2 + 3 + ... + 1001
。通常,如果在循环中每次迭代只连接一个字符(并且从一个字符长开始),如果迭代次数为 n,则将有2 + 3 + ... + n + 1
副本。这与 相同1 + 2 + 3 + ... + n = n(n+1)/2 = (n^2 + n)/2
,即 O(n^2)。
视情况而定。+ 有时可以降低代码的复杂性。考虑以下代码:
output = "<p>" + intro + "</p>";
这是一条很好的、清晰的线。不需要 String.Format。
如果你只使用 + 一次,你就没有缺点,而且它增加了可读性(正如 Colin Pickard 已经说过的)。
据我所知 + 表示:获取左操作数和右操作数并将它们复制到新缓冲区中(因为字符串是不可变的)。
所以使用 + 两次(如在 Colin Pickards 示例中,您已经创建了 2 个临时字符串。首先在添加"<p>"
到介绍时,然后在添加"</p>"
到新创建的字符串时。
您必须自己考虑何时使用哪种方法。如果 intro 是一个足够大的字符串,即使对于上面看到的一个小例子,性能下降也可能很严重。
我认为就复杂性而言,您可以重复使用新创建的字符串来解析格式字符串。例如"A" + "B" + "C" + "D"
,您必须复制“A”、“AB”,最后复制“ABC”才能形成“ABCD”。复制就是重复,对吧?因此,例如,如果您有一个 1000 个字符串,您将与千个字符串相加,您将复制(1000+N) 个字符串 1000 次。在最坏的情况下,它会导致 O(n^2) 复杂度。
Strin.Fomat,即使考虑解析,StringBuffer也应该在 O(n) 左右。
如果您要通过几个步骤构建大字符串,则应使用 StringBuilder。如果你知道它最终会有多大,这也是一件好事,然后你可以用你需要的大小来初始化它,并防止重新分配成本。对于小型操作,使用 + 运算符不会造成相当大的性能损失,并且会导致代码更清晰(并且编写速度更快......)
除非您的应用程序非常密集(配置文件、配置文件、配置文件!),否则这并不重要。优秀的程序员将可读性置于普通操作的性能之上。
因为字符串在 Java 和 C# 等语言中是不可变的,所以每次连接两个字符串时都必须创建一个新字符串,其中复制两个旧字符串的内容。
假设字符串平均长度为 c 个字符。
现在第一个连接只需要复制 2*c 个字符,但最后一个必须复制前 n-1 个字符串的连接,即 (n-1)*c 个字符长,最后一个本身,即c 个字符长,总共 n*c 个字符。对于 n 个连接,这会生成 n^2*c/2 个字符副本,这意味着 O(n^2) 的算法复杂度。
然而,在实践中的大多数情况下,这种二次复杂性并不明显(正如 Jeff Atwood 在 Robert C. Cartaino 链接的博客条目中所展示的那样),我建议尽可能编写可读的代码。
然而,在某些情况下它确实很重要,在这种情况下使用 O(n^2) 可能是致命的。
在实践中,我已经看到了这种方法,例如在内存中生成大的 Word XML 文件,包括 base64 编码的图片。由于使用 O(n^2) 字符串连接,这一代过去需要 10 多分钟。在我使用 + 与 StringBuilder 替换串联后,同一文档的运行时间减少到 10 秒以下。
同样,我见过一个软件,它使用 + 连接生成一段非常大的 SQL 代码作为字符串。我什至没有等到这个完成(已经等待了一个多小时),但只是使用 StringBuilder 重写了它。这个更快的版本在一分钟内完成。
简而言之,只需做最易读/最容易编写的任何事情,并且只在您要创建一个巨大的字符串时才考虑这一点:-)
编译器将优化:将“a”+“b”+“c”替换为 String.Concat 方法(不是 String.Format 一个作为固定我的评论)
已经有大量输入,但我一直认为解决性能问题的最佳方法是了解所有可行解决方案的性能差异,对于那些满足性能要求的解决方案,选择最可靠和最可支持的。
有很多人使用大 O 表示法来理解复杂性,但我发现在大多数情况下(包括了解哪种字符串连接方法效果最好),一个简单的时间试验就足够了。只需在 100,000 次迭代的循环中将 strA+strB 与 strA.Append(strB) 进行比较,看看哪个更快。
我很久以前就对它进行了基准测试,自 .NET 1.0 或 1.1 以来它并没有真正产生影响。
那时,如果您有一些进程要执行几百万次连接字符串的代码行,您可以通过使用 String.Concat、String.Format 或 StringBuilder 获得巨大的速度提升。
现在根本不重要了。至少自从 .Net 2.0 出现以来,这并不重要。把它从你的脑海中抛开,并以任何让你最容易阅读的方式编写代码。