我刚刚进入函数式编程并尝试理解何时应该使类/属性变得可变。
当处理大量字符串连接时,我们知道最好使用StringBuilder,例如:
using System;
using System.Diagnostics;
using System.Text;
namespace ConsoleApplication3
{
internal class Program
{
private static string myStr;
private static readonly StringBuilder mySb = new StringBuilder();
private static void Main(string[] args)
{
Profile("+", 100000, () => myStr = myStr + "a"); // Takes 2236 ms
Profile("SB", 100000, () => mySb.Append("a")); // Takes 1 ms
}
private static void Profile(string description, int iterations, Action func)
{
// clean up
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
// warm up
func();
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
}
}
这是众所周知的情况,通过StringBuilder
与+
运算符连接字符串,显着性能更高。我的假设是StringBuilder
通过创建更少的字符串来实现更好的性能。
性能和不变性之间是否存在平衡,或者出于某种原因这种情况是否例外?
答案 0 :(得分:6)
有效地连接字符串不是关于可变结构和不可变结构,而是关于选择正确的数据结构和评估策略来支持 O(1)追加。
通常,使用各种树的树来支持快速追加,从而最大化共享并最小化复制。示例结构包括ropes和finger trees。
在某些情况下,延迟评估也有帮助(例如,如果连接涉及复制,则可以延迟到实际需要字符串尾部的时间)。在这种情况下,严格的数据结构可能会产生额外的复制开销,需要做更多的工作。
在你的情况下,我怀疑+
涉及参数的严格副本(即 O(n + m))工作,而字符串构建器可以避免一些工作分摊字符串缓冲区的重新分配(提供树状性能,代价是需要线性使用结构,并且失去线程安全性)。