字符串加入和复杂性?

时间:2009-08-25 12:57:08

标签: c# string complexity-theory

当我需要连接两个字符串时,我使用String.Format(或StringBuilder,如果它发生在代码中的几个地方)。

我看到一些优秀的程序员不会注意加入复杂性的字符串,只使用'+'运算符。

我知道使用'+'运算符会使应用程序使用更多内存,但复杂性又如何呢?

12 个答案:

答案 0 :(得分:9)

这是一篇关于 Jeff Atwood 编码恐怖上的不同字符串连接方法的优秀文章:

alt text
(来源:codinghorror.com

The Sad Tragedy of Micro-Optimization Theater

这是帖子的要点。

  

[显示的几个字符串连接方法]

     

拿你发痒的小触发手指   关闭那个编译键并考虑一下   这一分钟。其中哪一项   方法会更快吗?

     

得到了答案?太好了!

     

并且......鼓声请...正确   回答:     

它。只是。不。物!

答案 1 :(得分:4)

这个答案假设您在谈论运行时复杂性。

使用+创建一个新的字符串对象,这意味着必须将旧字符串对象的内容复制到新字符串对象中。通过大量连接,例如在紧密循环中,这可以变成O(n ^ 2)操作。

作为一个非正式的证据,说你有以下代码:

string foo = "a";
for(int i = 0; i < 1000; i++)
{
    foo += "a";
}

循环的第一次迭代,首先将foo(“a”)的内容复制到一个新的字符串对象中,然后是文字“a”的内容。那是两份。第二次迭代有三个副本;两个来自新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)。

答案 2 :(得分:1)

取决于具体情况。 +有时可以降低代码的复杂性。请考虑以下代码:

output = "<p>" + intro + "</p>";

这是一条好的,明确的路线。不需要String.Format。

答案 3 :(得分:1)

如果你只使用+一次,你没有任何劣势,它增加了可读性(正如Colin Pickard所说)。

据我所知+表示:取左操作数和右操作数并将它们复制到新缓冲区(因为字符串是不可变的)。

所以使用+两次(如在Colin Pickards示例中,您已经创建了2个临时字符串。首先将"<p>"添加到介绍中,然后将"</p>"添加到新创建的字符串中。

您必须自己考虑何时使用哪种方法。即使是上面看到的一个小例子,如果intro是一个足够大的字符串,性能下降也会很严重。

答案 4 :(得分:1)

我认为在复杂性方面,您需要交换重新创建的新字符串来解析格式字符串。 对于axample "A" + "B" + "C" + "D"意味着您必须复制“A”,“AB”,并最后复制“ABC”以形成“ABCD”。复制是重复的,对吧?因此,例如,如果您有一个1000字符的字符串,您将与一千个字符串相加,您将复制(1000 + N)个字符串1000次。在最坏的情况下,它会导致O(n ^ 2)的复杂性。

Strin.Fomat ,即使考虑解析, StringBuffer 应该在O(n)附近。

答案 5 :(得分:1)

如果要在几个步骤中构建一个大字符串,则应使用StringBuilder。如果您知道它最终会有多大,那么这也是一件好事,然后您可以使用您需要的大小对其进行初始化,并防止重新分配成本。对于小型操作,使用+运算符不会导致相当大的性能损失,并且会产生更清晰的代码(写入速度更快......)

答案 6 :(得分:1)

除非你的应用程序是字符串密集型的(配置文件,配置文件,配置文件!),否则这并不重要。优秀的程序员将可读性置于高于平凡运营的性能之上。

答案 7 :(得分:1)

因为字符串在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重写了它。这个更快的版本在一分钟内完成。

简而言之,只要做一些最易读/最容易写的东西,只有在你创造一个巨大的字符串时才考虑这个: - )

答案 8 :(得分:0)

编译器将优化:“a”+“b”+“c”将被String.Concat方法替换(而不是String.Format一个修复我的评论)

答案 9 :(得分:0)

已经有很多投入,但我一直觉得解决性能问题的最佳方法是了解所有可行解决方案的性能差异,并且对于那些满足性能要求的解决方案,选择最可靠的解决方案最可支持的。

有很多人使用Big O符号来理解复杂性,但我发现在大多数情况下(包括理解哪种字符串连接方法效果最好),简单的时间试验就足够了。只需将strA + strB与strA.Append(strB)在100,000次迭代循环中进行比较,即可了解哪种工作更快。

答案 10 :(得分:0)

编译器将字符串文字串联优化为一个字符串文字。例如:

string s = "a" + "b" + "c";

在编译时针对以下内容进行了优化:

string s = "abc";

有关详细信息,请参阅this questionthis MSDN article

答案 11 :(得分:0)

我之前对此进行了基准测试,并且自.NET 1.0或1.1以来它并没有真正产生差异。

当时如果你有一些进程可以达到一系列代码,这些代码能够将字符串连接几百万次,那么使用String.Concat,String.Format或StringBuilder可以大大提高速度。

现在根本没关系。自从.Net 2.0无论如何出现以来,至少它并不重要。把它放在你的脑海和代码之外,以最简单的方式让你阅读。