我是否破坏了StringBuilder的效率?

我已经开始使用StringBuilder而不是直接连接,但是它似乎缺less一个关键的方法。 所以,我自己实现它作为一个扩展:

 public void Append(this StringBuilder stringBuilder, params string[] args) { foreach (string arg in args) stringBuilder.Append(arg); } 

这变成了以下混乱:

 StringBuilder sb = new StringBuilder(); ... sb.Append(SettingNode); sb.Append(KeyAttribute); sb.Append(setting.Name); 

进入这个:

 sb.Append(SettingNode, KeyAttribute, setting.Name); 

我可以使用sb.AppendFormat("{0}{1}{2}",... ,但是这看起来更不理想,而且更难读。我的扩展是一个好方法,还是以某种方式破坏了好处的StringBuilder ?我不是试图过早地优化任何东西,因为我的方法更多的是可读性而不是速度,但我也想知道我不是在自己的脚下射击。

我看到你的扩展没有问题。 如果它适合你,那就很好。

我自己prefere:

 sb.Append(SettingNode) .Append(KeyAttribute) .Append(setting.Name); 

像这样的问题总是可以用简单的testing案例来回答。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; namespace SBTest { class Program { private const int ITERATIONS = 1000000; private static void Main(string[] args) { Test1(); Test2(); Test3(); } private static void Test1() { var sw = Stopwatch.StartNew(); var sb = new StringBuilder(); for (var i = 0; i < ITERATIONS; i++) { sb.Append("TEST" + i.ToString("00000"), "TEST" + (i + 1).ToString("00000"), "TEST" + (i + 2).ToString("00000")); } sw.Stop(); Console.WriteLine("Testing Append() extension method..."); Console.WriteLine("--------------------------------------------"); Console.WriteLine("Test 1 iterations: {0:n0}", ITERATIONS); Console.WriteLine("Test 1 milliseconds: {0:n0}", sw.ElapsedMilliseconds); Console.WriteLine("Test 1 output length: {0:n0}", sb.Length); Console.WriteLine(""); } private static void Test2() { var sw = Stopwatch.StartNew(); var sb = new StringBuilder(); for (var i = 0; i < ITERATIONS; i++) { sb.Append("TEST" + i.ToString("00000")); sb.Append("TEST" + (i+1).ToString("00000")); sb.Append("TEST" + (i+2).ToString("00000")); } sw.Stop(); Console.WriteLine("Testing multiple calls to Append() built-in method..."); Console.WriteLine("--------------------------------------------"); Console.WriteLine("Test 2 iterations: {0:n0}", ITERATIONS); Console.WriteLine("Test 2 milliseconds: {0:n0}", sw.ElapsedMilliseconds); Console.WriteLine("Test 2 output length: {0:n0}", sb.Length); Console.WriteLine(""); } private static void Test3() { var sw = Stopwatch.StartNew(); var sb = new StringBuilder(); for (var i = 0; i < ITERATIONS; i++) { sb.AppendFormat("{0}{1}{2}", "TEST" + i.ToString("00000"), "TEST" + (i + 1).ToString("00000"), "TEST" + (i + 2).ToString("00000")); } sw.Stop(); Console.WriteLine("Testing AppendFormat() built-in method..."); Console.WriteLine("--------------------------------------------"); Console.WriteLine("Test 3 iterations: {0:n0}", ITERATIONS); Console.WriteLine("Test 3 milliseconds: {0:n0}", sw.ElapsedMilliseconds); Console.WriteLine("Test 3 output length: {0:n0}", sb.Length); Console.WriteLine(""); } } public static class SBExtentions { public static void Append(this StringBuilder sb, params string[] args) { foreach (var arg in args) sb.Append(arg); } } } 

在我的电脑上,输出是:

 Testing Append() extension method... -------------------------------------------- Test 1 iterations: 1,000,000 Test 1 milliseconds: 1,080 Test 1 output length: 29,700,006 Testing multiple calls to Append() built-in method... -------------------------------------------- Test 2 iterations: 1,000,000 Test 2 milliseconds: 1,001 Test 2 output length: 29,700,006 Testing AppendFormat() built-in method... -------------------------------------------- Test 3 iterations: 1,000,000 Test 3 milliseconds: 1,124 Test 3 output length: 29,700,006 

所以你的扩展方法只比Append()方法慢一些,比AppendFormat()方法稍微快一些,但是在所有这三种情况下,这个差别完全是太微不足道了。 因此,如果您的扩展方法增强了您的代码的可读性,请使用它!

创造额外的数组是一点点的开销,但我怀疑这是很多。 你应该测量

如果事实certificate,创buildstring数组的开销是显着的,你可以缓解它有几个重载 – 一个为两个参数,一对三,一对四等…所以,只有当你达到更高的数量参数(例如六个或七个)将需要创build数组。 重载会是这样的:

 public void Append(this builder, string item1, string item2) { builder.Append(item1); builder.Append(item2); } public void Append(this builder, string item1, string item2, string item3) { builder.Append(item1); builder.Append(item2); builder.Append(item3); } public void Append(this builder, string item1, string item2, string item3, string item4) { builder.Append(item1); builder.Append(item2); builder.Append(item3); builder.Append(item4); } // etc 

然后最后一个超载使用params ,例如

 public void Append(this builder, string item1, string item2, string item3, string item4, params string[] otherItems) { builder.Append(item1); builder.Append(item2); builder.Append(item3); builder.Append(item4); foreach (string item in otherItems) { builder.Append(item); } } 

我当然期望这些(或只是你原来的扩展方法)比使用AppendFormat更快 – AppendFormat它需要parsing格式string。

请注意,我没有让这些重载以伪recursion的方式互相调用 – 我怀疑它们是内联的,但是如果它们不是build立一个新的栈框架等的开销,那么最终可能是非常重要的。 (如果我们已经有了这个,我们假设数组的开销很大。)

除了一点开销之外,我个人也没有看到任何问题。 绝对更可读。 只要你传递了合理数量的参数,我就不会看到问题。

从清晰的angular度来看,你的扩展是可以的。

如果您从未拥有超过5或6个项目,最好只使用.append(x).append(y).append(z)格式。

如果你正在处理数以千计的项目,StringBuilder本身只会为你提供一个性能提升。 另外,每次调用方法时都会创build数组。

所以,如果你为了清晰起见,那没关系。 如果你为了效率而做这件事,那么你很可能走错了路。

我不会说你正在破坏它的效率,但是如果有更高效的方法,你可能会做一些效率低下的事情。 AppendFormat是我认为你想在这里。 如果不断使用的{0} {1} {2}string太丑,我倾向于把我的格式string放在上面的常量中,所以看起来会和扩展名差不多。

 sb.AppendFormat(SETTING_FORMAT, var1, var2, var3); 

我最近没有testing过,但是在过去,StringBuilder实际上比普通的香草串连接(“this”+“that”)要慢,直到大约7个连接。

如果这是不在循环中发生的string连接,则可能需要考虑是否应该使用StringBuilder。 (在一个循环中,由于string是不可变的,我开始担心用纯粹的string连接进行分配。)

可能更快,因为它至多执行一次重新分配/复制步骤,对于许多附加。

 public void Append(this StringBuilder stringBuilder, params string[] args) { int required = stringBuilder.Length; foreach (string arg in args) required += arg.Length; if (stringBuilder.Capacity < required) stringBuilder.Capacity = required; foreach (string arg in args) stringBuilder.Append(arg); } 

最终归结到哪一个会导致更less的string创build。 我有一种感觉,扩展将导致使用string格式更高的string数。 但是performance可能不会那么不同。

克里斯,

受Jon Skeet的回应 (第二个答案)的启发,我稍微重写了你的代码。 基本上,我添加了运行传入函数的TestRunner方法,并报告所用时间,消除了一些冗余代码。 不要自鸣得意,而是作为自己的编程练习。 我希望这是有帮助的。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; namespace SBTest { class Program { private static void Main(string[] args) { // JIT everything AppendTest(1); AppendFormatTest(1); int iterations = 1000000; // Run Tests TestRunner(AppendTest, iterations); TestRunner(AppendFormatTest, iterations); Console.ReadLine(); } private static void TestRunner(Func<int, long> action, int iterations) { GC.Collect(); var sw = Stopwatch.StartNew(); long length = action(iterations); sw.Stop(); Console.WriteLine("--------------------- {0} -----------------------", action.Method.Name); Console.WriteLine("iterations: {0:n0}", iterations); Console.WriteLine("milliseconds: {0:n0}", sw.ElapsedMilliseconds); Console.WriteLine("output length: {0:n0}", length); Console.WriteLine(""); } private static long AppendTest(int iterations) { var sb = new StringBuilder(); for (var i = 0; i < iterations; i++) { sb.Append("TEST" + i.ToString("00000"), "TEST" + (i + 1).ToString("00000"), "TEST" + (i + 2).ToString("00000")); } return sb.Length; } private static long AppendFormatTest(int iterations) { var sb = new StringBuilder(); for (var i = 0; i < iterations; i++) { sb.AppendFormat("{0}{1}{2}", "TEST" + i.ToString("00000"), "TEST" + (i + 1).ToString("00000"), "TEST" + (i + 2).ToString("00000")); } return sb.Length; } } public static class SBExtentions { public static void Append(this StringBuilder sb, params string[] args) { foreach (var arg in args) sb.Append(arg); } } } 

这是输出:

 --------------------- AppendTest ----------------------- iterations: 1,000,000 milliseconds: 1,274 output length: 29,700,006 --------------------- AppendFormatTest ----------------------- iterations: 1,000,000 milliseconds: 1,381 output length: 29,700,006