哪种字符串操作更好?

7
可能是重复问题:
字符串连接与字符串生成器。 性能

< p >以下两个选项之间有任何差异(性能和内存使用)吗?

选项1:

StringBuilder msgEntry = new StringBuilder();
msgEntry.AppendLine("<" + timeTag + ">" + timeStamp + "</" + timeTag + ">");

选项2:

StringBuilder msgEntry = new StringBuilder();
msgEntry.Append("<");
msgEntry.Append(timeTag);
msgEntry.Append(">");
msgEntry.Append(timeStamp);
msgEntry.Append("</");
msgEntry.Append(timeTag );
msgEntry.Append(">\n");

4
除非你已经发现了性能问题,或者这段代码在循环中执行了数千次,否则两种方式的差异并不重要。选择最易读的选项即可。 - JohnFx
1
考虑使用一些现有的XML类可能是值得的。听起来您可能不熟悉许多框架(因为您不知道字符串格式函数),我建议您可以松散地阅读一个21天的书籍,以了解这些内容。 - overslacked
1
我投票支持重新开放:当未在循环中使用时,性能情况是不同的!在这种特定情况下,字符串连接(选项1)将使用单个调用String.Concat,其性能肯定比七个调用StringBuilder.Append要好。我正要写一个答案指出这一点... - MartinStettner
@MartinStettner:正如我在评论中提到的,我认为确定哪个更好是不可行的。有很多微妙之处。然而,我也投票重新打开:它不仅没有在循环中连接,而且最终结果是一个StringBuilder,而不是一个String,这又产生了另一个区别。 - Jon Skeet
@Jon:没错。因此,第一选择可能更好(尽管所有建议都是如果性能很重要,则始终使用 StringBuilder!) - MartinStettner
10个回答

27

第二种方式在内存使用方面可能会稍微好一些,因为它不需要计算中间字符串1......但在我看来,它的可读性较差。

个人而言,我会使用:

msgEntry.AppendFormat("<{0}>{1}</{0}>", timeTag, timeStamp);

您没有展示您接下来想使用 StringBuilder 做什么。如果您只是想将其转换成字符串,那我会使用:

string text = string.Format("<{0}>{1}</{0}>", timeTag, timeStamp);

首先要说的是。性能如何?嗯,可能会更差 - 毕竟它还需要解析格式字符串。但是除非你已经测量过并发现这是瓶颈,否则你为什么要担心呢?

总的来说:

  • 确保你的 架构 足够高效 - 那很难在后期进行更改。
  • 在内部 设计 中平衡效率和简洁性,注重可测试性;更改设计可能需要一段时间,但通常应该不会出现兼容性问题。
  • 编写尽可能易读的 实现
  • 对系统进行 测量,找出是否表现足够好以及瓶颈在哪里。它们几乎永远不会出现在像这样的代码中(毕竟我们不是在讨论在循环中的字符串连接)。
  • 当您找到瓶颈时,请尝试不同的优化方法并 再次测量。不要假设您认为会更快的东西实际上会更快。

1 或者传递给 Concat 的数组……我们不知道 timeStamp 的类型,因此无法确定发生了什么;在第二种形式中,它可能会被就地附加,而第一种形式可能需要将其装箱并在执行连接之前将其转换为字符串。

实现收缩等的确切实现在 .NET 3.5 和 .NET 4 之间可能已经发生了变化(我知道某些实现部分已经发生了变化)。除非进行非常仔细的基准测试,否则我绝不愿意说哪个更快......但可读性更容易被评估,尽管这是主观的。


+1 一如既往...领先一步。 - Justin Niessner
+1 的性能瓶颈是一个巧妙的解释。 - P.Brian.Mackey
我认为我们需要对Jon设置一个障碍。也许可以延迟45秒显示任何问题,给其他人一个领先的机会.. ;) - NotMe
@MartinStettner:我不确定。首先,第一种形式必须创建一个object[]以传递给Concat。根据timeStamp的类型,可能涉及装箱 - 这可能发生或不发生在Append代码中,具体取决于确切的类型。根据细节(包括框架版本),第二种形式可能会有更多的重新分配,也可能没有。基本上,我认为这太难了,除非我有充分的理由,否则我不会担心它。 - Jon Skeet
@Martin:我很乐意接受这个。当然,在这种情况下,总是涉及到一个StringBuilder,这使得它有些不寻常。 - Jon Skeet
显示剩余11条评论

3

一般来说,StringBuilder... 但是在讨论性能时,唯一真正的测试是测量。特别是对于大量字符串更改,使用 StringBuilder 绝对是正确的选择。对于几个字符串... 直接使用 + 运算符拼接可能更加简单。


+1 是指出当你有大量字符串连接时,StringBuilder 总是更好的。 - Jagd
不是这种情况!对于单个连接(在循环之外),第一种选项将进行一次String.Concat调用。第二个选项需要进行七次StringBuilder.Append调用。 - MartinStettner
@Martin:“通常来说”。Jon表达了相同的观点,虽然比我更雄辩,详细。但是主旨是相同的。 - Steve

2
在这种情况下,我会使用.AppendFormat();
StringBuilder msgEntry = new StringBuilder();
msgEntry.AppendFormat("<{0}>{1}</{0}>", timeTag , timeStamp);

+1 我以前从未注意到 StringBuilder 上的那个方法。太棒了!我不再需要使用 sb.append(format(...)) 了! - JohnFx

2

个人而言,我不会选择任何一个,而是使用

msgEntry.AppendFormat("<{0}>{1}</{0}>", timeTag, timeStamp);

1

如果你只是做这个,不要使用StringBuilder。它会增加太多开销并影响可读性。

试试这个:

string.Format("<{0}>{1}</{2}>", timeTag, timeStamp, timeTag);

1

我一直依赖于MSDN的.NET应用程序性能技巧中的建议,该建议是在复杂字符串操作时使用StringBuilder。

它继续提出建议:

权衡 创建StringBuilder对象会带来一些开销,包括时间和内存。 在具有快速内存的计算机上,如果您执行大约五个操作,则使用StringBuilder变得值得。 根据经验法则,我会说10个或更多的字符串操作是任何计算机(即使是较慢的计算机)都可以承受开销的理由。

我还会考虑来自代码优化.Net show的这个建议:

特别重要的是预先分配字符串的大小。 如果不这样做,StringBuilder仍然更快,但如果您可以预测最终字符串的长度,请提前设置它。

那是因为 StringBuilder 的默认容量为 16。当容量超出时,它会自动调整大小——每次增加一倍。因此,如果您不设置初始容量,则可能会有几次不必要的调整大小。您可以计算出示例中预期的最大字符数,并初始化 StringBuilder,以便它不会调整大小。这将节省一些 CPU。
这里还有来自 MSDN 的一些额外建议: link1 String或StringBuilder对象的连接操作的性能取决于内存分配的频率。字符串连接操作总是会分配内存,而StringBuilder连接操作仅在StringBuilder对象缓冲区太小以容纳新数据时才分配内存。因此,如果要连接固定数量的字符串对象,则首选String类进行连接操作。在这种情况下,编译器甚至可以将单个连接操作合并为单个操作。如果要连接任意数量的字符串,例如,如果循环连接用户输入的随机数量的字符串,则StringBuilder对象更适合进行连接操作。

我会先弄清楚这是否真的重要,然后再担心计算正确的预分配大小等问题。值得注意的是,你提供的第一个链接是在2001年8月发布的......甚至在.NET 1.0发布之前。我知道StringBuilder的实现在.NET 3.5和.NET 4之间发生了显著变化,我也不会惊讶它在其他版本中也有所改变。当然,仍然值得避免在循环中进行字符串连接......但是经验法则肯定随着时间而改变。 - Jon Skeet
@Jon Skeet 我知道第一篇文章不是最近的,但它仍经常被引用,如果它不再有效,我认为微软工作人员会将其删除或更新。在第三篇文章中,适用于 .Net 4,他们在“性能考虑”中提到了调整大小。由于在这种情况下有时很容易计算 StringBuilder 所需的最大容量,因此如果应用程序中频繁进行字符串连接,我肯定会这样做。这只是一个小小的努力。 - DOK
多频繁才算频繁?除非你有充分的理由,否则我不会这样做。在这种情况下,你认为需要付出多少努力?当你阅读代码时呢?你将如何计算timeStamp的长度? - Jon Skeet

0
第二行更好,因为在第一行中,使用字符串构建器没有太多或者完全没有好处。但是对于这样一个小的字符串连接,我不会费心去使用字符串构建器。

0

是的。

如果您要选择选项1,那么使用StringBuilder就没有意义了。您仍然在进行字符串连接,并最终会创建和丢弃多个额外的临时字符串。

对于这样的情况,您应该使用String.Format()


这里仅需要一个额外的临时字符串 - 编译器将调用 string.Concat("<", timeTag, ">", timeStamp, "</", timeTag, ">")(必要时进行转换)。 - Jon Skeet

0

我认为第一个例子是错误的。

在将字符串连接到 StringBuilder 之前,创建 StringBuilder 对象有什么意义呢?

另外请注意:

  • Append 返回 StringBuilder 的实例,因此可以将 Append 和 AppendLine 调用链接在一起
  • 在第二个示例的末尾,您可以使用 AppendLine(">") 替换 Append(">\n")
    StringBuilder msgEntry = new StringBuilder();
    msgEntry.Append("<")
        .Append(timeTag)
        .Append(">")
        .Append(timeStamp)
        .Append("</")
        .Append(timeTag )
        .AppendLine(">");

个人而言,我会这样做

    string.Format("<{0}>{1}</{0}>", timeTag, timeStamp);

除非您需要 StringBuilder 做其他事情。


-1

在字符串拼接时始终使用 StringBuilder.Append()。字符串的 + 操作符会为每个新项创建新的分配。


1
不会。它只会调用一次 string.Concat,因此它将生成一个额外的中间字符串。只有在实际需要时才应使用 StringBuilder.Append() - 如果你正在单个表达式中完成所有拼接并希望最终输出一个字符串,则使用 + 完全没有问题。 - Jon Skeet
好的,我刚刚进行了一个简短的测试。显然我错了。感谢您的纠正。 - Florian Reischl
如果您有相邻的静态字符串,用“+”分隔,编译器将在编译时将它们组合起来。此外,如果只是连接2或3个字符串,“+”比实例化一个stringbuilder对象更快。 - adam0101

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