我想知道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);
答案 0 :(得分:40)
不可能说,不知道string1
和string2
的大小。
通过调用AppendFormat
,只要给定格式字符串的长度和将要插入的字符串,它就会预先分配缓冲区,然后连接所有内容并将其插入缓冲区。对于非常大的字符串,这将比单独调用Append
更有利,这可能导致缓冲区多次扩展。
但是,对Append
的三次调用可能会也可能不会触发缓冲区的增长,并且每次调用都会执行该检查。如果字符串足够小并且没有触发缓冲区扩展,那么它将比调用AppendFormat
更快,因为它不必解析格式字符串以找出替换位置。
确定答案需要更多数据
应该注意的是,很少讨论使用静态Concat
method on the String
class(Jon's answer使用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
,然后运行下一次循环相同次数的测试,创建一个< em>额外的 1,000,000 StringBuilder
个实例,GC很可能会在第二次测试中介入并阻碍其计时。
答案 1 :(得分:22)
casperOne is correct。达到特定阈值后,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's answer。
修改强>
我在Release配置下单独重新测试每个测试并更新结果。
答案 2 :(得分:12)
casperOne is entirely accurate that it depends on the data。但是,假设您将此作为第三方使用的类库编写 - 您会使用哪个?
一种选择是充分利用两个世界 - 计算出你实际需要添加多少数据,然后使用StringBuilder.EnsureCapacity确保我们只需要一个缓冲区调整大小。< / p>
如果我不是太,我会使用Append
x3 - 似乎“更有可能”更快,因为在每次调用时解析字符串格式标记是明确地做好工作。
请注意,我已经向BCL团队询问了一种“缓存格式化程序”,我们可以使用格式字符串创建,然后重复使用。令人发疯的是,框架每次使用时都必须解析格式字符串。
编辑:好的,我在某种程度上编辑了John的代码以获得灵活性,并添加了一个“AppendWithCapacity”,它首先确定了必要的容量。以下是不同长度的结果 - 对于长度1,我使用了1,000,000次迭代;对于所有其他长度,我使用了100,000。 (这只是为了获得合理的运行时间。)所有时间都是毫秒。不幸的是,桌面并没有真正起作用。长度为1,1000,10000,20000
时间:
所以当它发生时,我从来没有看到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];
Action<string,string> action;
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);
}
}
答案 3 :(得分:6)
Append
会更快,因为该方法有许多重载,允许编译器调用正确的方法。由于您使用Strings
,StringBuilder
可以使用String
重载来Append
。
AppendFormat
需要String
,然后是Object[]
,这意味着必须解析格式,并且数组中的每个Object
必须为{{1}在它可以添加到ToString'd
内部数组之前。
注意:至于casperOne的观点 - 如果没有更多数据,很难给出确切的答案。
答案 4 :(得分:2)
StringBuilder
也有级联附加:Append()
会返回StringBuilder
本身,因此您可以像这样编写代码:
StringBuilder sb = new StringBuilder();
sb.Append(string1)
.Append("----")
.Append(string2);
清理,它产生更少的IL代码(尽管这实际上是一种微优化)。
答案 5 :(得分:1)
当然,在每种情况下都要确定一下。
那就是说,我认为一般来说它将是前者,因为你没有重复解析格式字符串。
然而,差异将非常小。在大多数情况下,你真的应该考虑使用AppendFormat
。
答案 6 :(得分:0)
我认为这是最少量工作的电话。附加只是连接字符串,其中AppendFormat正在进行字符串替换。当然,这些天你永远不能说......
答案 7 :(得分:0)
1应该更快,因为它只是附加字符串,而2必须根据格式创建一个字符串,然后附加字符串。所以那里还有一个额外的步骤。
答案 8 :(得分:0)
在你的情况下,速度快1,但这不是一个公平的比较。你应该问StringBuilder.AppendFormat()vs StringBuilder.Append(string.Format()) - 由于内部使用char数组,第一个更快。
你的第二个选择更具可读性。