我想知道StringBuilder,我有一个问题,我希望社区能够解释.
让我们忘记代码可读性,哪些更快,为什么?
StringBuilder.Append
:StringBuilder sb = new StringBuilder(); sb.Append(string1); sb.Append("----"); sb.Append(string2);
StringBuilder.AppendFormat
:StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0}----{1}",string1,string2);
casperOne.. 41
这是不可能的说,不知道的大小string1
和string2
.
通过调用AppendFormat
,它将在给定格式字符串的长度和将要插入的字符串的情况下预分配缓冲区,然后连接所有内容并将其插入缓冲区.对于非常大的字符串,这将比Append
可能导致缓冲区多次扩展的单独调用更有利.
但是,三次调用Append
可能会或可能不会触发缓冲区的增长,并且每次调用都会执行该检查.如果字符串足够小并且没有触发缓冲区扩展,那么它将比调用更快,AppendFormat
因为它不必解析格式字符串以找出替换位置.
确定答案需要更多数据
应该注意的是,Concat
关于在String
课堂上使用静态方法的讨论很少(Jon的答案使用了AppendWithCapacity
我的提醒).他的测试结果表明这是最好的情况(假设您不必利用特定的格式说明符). String.Concat
做同样的事情,因为它将预先确定连接和预分配缓冲区的字符串的长度(由于通过参数的循环结构,稍微增加了开销).它的性能将与Jon的AppendWithCapacity
方法相媲美.
或者,只是普通的加法运算符,因为它编译为String.Concat
无论如何调用,但需要注意的是所有的加法都在同一个表达式中:
// One call to String.Concat. string result = a + b + c;
不
// Two calls to String.Concat. string result = a + b; result = result + c;
对于那些提出测试代码的人
您需要在单独的运行中运行测试用例(或者至少在测量单独的测试运行之间执行GC).这样做的原因是,如果你说,1,000,000次运行,StringBuilder
在一次测试的循环的每次迭代中创建一个新的,然后你运行下一个循环相同次数的测试,创建额外的 1,000,000个StringBuilder
实例,GC很可能会在第二次测试中介入并阻碍其时间安排.
这是不可能的说,不知道的大小string1
和string2
.
通过调用AppendFormat
,它将在给定格式字符串的长度和将要插入的字符串的情况下预分配缓冲区,然后连接所有内容并将其插入缓冲区.对于非常大的字符串,这将比Append
可能导致缓冲区多次扩展的单独调用更有利.
但是,三次调用Append
可能会或可能不会触发缓冲区的增长,并且每次调用都会执行该检查.如果字符串足够小并且没有触发缓冲区扩展,那么它将比调用更快,AppendFormat
因为它不必解析格式字符串以找出替换位置.
确定答案需要更多数据
应该注意的是,Concat
关于在String
课堂上使用静态方法的讨论很少(Jon的答案使用了AppendWithCapacity
我的提醒).他的测试结果表明这是最好的情况(假设您不必利用特定的格式说明符). String.Concat
做同样的事情,因为它将预先确定连接和预分配缓冲区的字符串的长度(由于通过参数的循环结构,稍微增加了开销).它的性能将与Jon的AppendWithCapacity
方法相媲美.
或者,只是普通的加法运算符,因为它编译为String.Concat
无论如何调用,但需要注意的是所有的加法都在同一个表达式中:
// One call to String.Concat. string result = a + b + c;
不
// Two calls to String.Concat. string result = a + b; result = result + c;
对于那些提出测试代码的人
您需要在单独的运行中运行测试用例(或者至少在测量单独的测试运行之间执行GC).这样做的原因是,如果你说,1,000,000次运行,StringBuilder
在一次测试的循环的每次迭代中创建一个新的,然后你运行下一个循环相同次数的测试,创建额外的 1,000,000个StringBuilder
实例,GC很可能会在第二次测试中介入并阻碍其时间安排.
casperOne是对的.一旦达到某个阈值,该Append()
方法就会慢于AppendFormat()
.以下是每种方法100,000次迭代的不同长度和经过的时间间隔:
Append() - 50900 AppendFormat() - 126826
Append() - 1241938 AppendFormat() - 1337396
Append() - 12482051 AppendFormat() - 12740862
Append() - 61029875 AppendFormat() - 60483914
当引入长度接近20,000的字符串时,该AppendFormat()
函数将略微超越Append()
.
为什么会这样?请参阅casperOne的回答.
编辑:
我在Release配置下单独重新测试每个测试并更新结果.
casperOne完全准确,取决于数据.但是,假设您将此作为第三方使用的类库编写 - 您会使用哪个?
一种选择是充分利用两个世界 - 计算出你实际需要附加多少数据,然后使用StringBuilder.EnsureCapacity来确保我们只需要一个缓冲区调整大小.
如果我不是太烦恼,我会使用Append
x3 - 似乎"更有可能"更快,因为在每个调用中解析字符串格式标记显然是make-work.
请注意,我已经向BCL团队询问了一种"缓存格式化程序",我们可以使用格式字符串创建,然后重复使用.框架必须在每次使用时解析格式字符串,这很疯狂.
编辑:好的,我在某种程度上编辑了John的代码以获得灵活性,并添加了一个"AppendWithCapacity",它首先确定了必要的容量.以下是不同长度的结果 - 对于长度1,我使用了1,000,000次迭代; 对于所有其他长度,我使用了100,000.(这只是为了获得合理的运行时间.)所有时间都是毫安.
不幸的是,桌子并没有真正起作用.长度分别为1,1000,10000,20000
时报:
追加:162,475,7997,17970
AppendFormat:392,499,8541,18993
AppendWithCapacity:139,189,1558,3085
事实上,我从未见过AppendFormat击败Append - 但我确实看到AppendWithCapacity以非常大的优势获胜.
这是完整的代码:
using System; using System.Diagnostics; using System.Text; public class StringBuilderTest { static void Append(string string1, string string2) { StringBuilder sb = new StringBuilder(); sb.Append(string1); sb.Append("----"); sb.Append(string2); } static void AppendWithCapacity(string string1, string string2) { int capacity = string1.Length + string2.Length + 4; StringBuilder sb = new StringBuilder(capacity); sb.Append(string1); sb.Append("----"); sb.Append(string2); } static void AppendFormat(string string1, string string2) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0}----{1}", string1, string2); } static void Main(string[] args) { int size = int.Parse(args[0]); int iterations = int.Parse(args[1]); string method = args[2]; Actionaction; switch (method) { case "Append": action = Append; break; case "AppendWithCapacity": action = AppendWithCapacity; break; case "AppendFormat": action = AppendFormat; break; default: throw new ArgumentException(); } string string1 = new string('x', size); string string2 = new string('y', size); // Make sure it's JITted action(string1, string2); GC.Collect(); Stopwatch sw = Stopwatch.StartNew(); for (int i=0; i < iterations; i++) { action(string1, string2); } sw.Stop(); Console.WriteLine("Time: {0}ms", (int) sw.ElapsedMilliseconds); } }
Append
在大多数情况下会更快,因为该方法有许多重载,允许编译器调用正确的方法.由于您使用Strings
的StringBuilder
可使用String
的过载Append
.
AppendFormat
取一个String
然后一个Object[]
意味着必须解析格式Object
,并且数组中的每个都必须ToString'd
在它被添加到StringBuilder's
内部数组之前.
注意:对于casperOne的观点 - 如果没有更多数据,很难给出确切的答案.