String.Format和StringBuilder一样高效
假设我在C#中有一个stringbuilder,它是这样做的:
StringBuilder sb = new StringBuilder(); string cat = "cat"; sb.Append("the ").Append(cat).(" in the hat"); string s = sb.ToString();
那么效率会更高,或者更高效:
string cat = "cat"; string s = String.Format("The {0} in the hat", cat);
如果是这样,为什么?
编辑
经过一些有趣的回答,我意识到我应该在我所问的问题上更清楚些。 我并不是要求串联一个string的速度更快,而是将一个string注入另一个string更快。
在上面的两种情况下,我想注入一个或多个string到预定义的模板string的中间。
对困惑感到抱歉
String.Format
内部使用StringBuilder
:
public static string Format(IFormatProvider provider, string format, params object[] args) { if ((format == null) || (args == null)) { throw new ArgumentNullException((format == null) ? "format" : "args"); } StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8)); builder.AppendFormat(provider, format, args); return builder.ToString(); }
上面的代码是mscorlib的代码片段,所以问题变成“比StringBuilder.AppendFormat()
更快的是StringBuilder.AppendFormat()
”?
如果没有基准testing,我可能会说上面的代码示例使用.Append()
更快地运行。 但是这是一个猜测,试着对两者进行基准testing和/或分析,以得到适当的比较。
杰里·迪克森(Jerry Dixon)做了一些基准testing:
http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm
更新:
可悲的是上面的链接已经死了。 然而,在回机器上仍然有一个副本:
在一天结束的时候,它取决于你的string格式是否会被重复调用,也就是说你正在处理超过100兆字节文本的严肃文本处理,或者当用户一再单击button时被调用。 除非你正在做大量的批处理工作,否则我会坚持使用String.Format,它有助于代码的可读性。 如果您怀疑perf性能瓶颈,那么在您的代码上粘贴一个profiler,看看它到底在哪里。
从MSDN文档 :
String或StringBuilder对象的连接操作的性能取决于内存分配的频率。 如果StringBuilder对象缓冲区太小而不能容纳新数据,则String连接操作始终分配内存,而StringBuilder连接操作仅分配内存。 因此,如果串联固定数量的String对象,那么String类对于连接操作来说是可取的。 在这种情况下,编译器甚至可以将各个级联操作组合成单个操作。 如果连接任意数量的string,StringBuilder对象可以用于连接操作; 例如,如果一个循环连接了随机数的用户inputstring。
我运行了一些快速的性能基准testing,对于10次运行的平均10万次操作,第一种方法(string生成器)几乎是第二次(string格式)的一半时间。
所以,如果这种情况很less发生,那没关系。 但是,如果这是一种常见的操作,那么您可能需要使用第一种方法。
我期望String.Format慢 – 它必须parsingstring, 然后连接它。
一对笔记:
- 格式是专业应用程序中用户可见string的方式; 这避免了本地化的错误
- 如果事先知道结果string的长度,请使用StringBuilder(Int32)构造函数来预定义容量
我认为在大多数情况下,这种清晰而不是效率,应该是你最关心的问题。 除非你把大量的琴弦压在一起,或者为低能耗的移动设备搭build一些东西,否则这可能不会让你的运行速度大打折扣。
我发现,在我以相当线性的方式构buildstring的情况下,无论是直接连接还是使用StringBuilder都是最佳select。 我build议在这种情况下,大部分的string是dynamic的。 由于很less的文本是静态的,所以最重要的是很清楚每个dynamic文本的放置位置,以防将来需要更新。
另一方面,如果你正在谈论的是大量的静态文本,其中有两个或三个variables,即使效率稍差,我认为从string.Format获得的清晰度也是值得的。 我在本周早些时候曾经在一个4页文档的中心放置了一个dynamic文本。 如果它更新一大块文本比更新连接在一起的三块文件更容易。
如果只是因为string.Format并不完全符合你的想法,那么在Net45上重新运行6年后的testing。
Concat仍然是最快的,但实际上不到30%的差距。 StringBuilder和Format仅相差5-10%。 我有几次运行testing20%的变化。
毫秒,一百万次迭代:
- 连接:367
- 新的stringBuilder为每个关键:452
- caching的StringBuilder:419
- string.Format:475
我拿走的教训是性能差异是微不足道的,所以它不应该阻止你编写最简单的可读代码。 这对我的钱往往是,而不是总是a + b + c
。
const int iterations=1000000; var keyprefix= this.GetType().FullName; var maxkeylength=keyprefix + 1 + 1+ Math.Log10(iterations); Console.WriteLine("KeyPrefix \"{0}\", Max Key Length {1}",keyprefix, maxkeylength); var concatkeys= new string[iterations]; var stringbuilderkeys= new string[iterations]; var cachedsbkeys= new string[iterations]; var formatkeys= new string[iterations]; var stopwatch= new System.Diagnostics.Stopwatch(); Console.WriteLine("Concatenation:"); stopwatch.Start(); for(int i=0; i<iterations; i++){ var key1= keyprefix+":" + i.ToString(); concatkeys[i]=key1; } Console.WriteLine(stopwatch.ElapsedMilliseconds); Console.WriteLine("New stringBuilder for each key:"); stopwatch.Restart(); for(int i=0; i<iterations; i++){ var key2= new StringBuilder(keyprefix).Append(":").Append(i.ToString()).ToString(); stringbuilderkeys[i]= key2; } Console.WriteLine(stopwatch.ElapsedMilliseconds); Console.WriteLine("Cached StringBuilder:"); var cachedSB= new StringBuilder(maxkeylength); stopwatch.Restart(); for(int i=0; i<iterations; i++){ var key2b= cachedSB.Clear().Append(keyprefix).Append(":").Append(i.ToString()).ToString(); cachedsbkeys[i]= key2b; } Console.WriteLine(stopwatch.ElapsedMilliseconds); Console.WriteLine("string.Format"); stopwatch.Restart(); for(int i=0; i<iterations; i++){ var key3= string.Format("{0}:{1}", keyprefix,i.ToString()); formatkeys[i]= key3; } Console.WriteLine(stopwatch.ElapsedMilliseconds); var referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway= concatkeys.Union(stringbuilderkeys).Union(cachedsbkeys).Union(formatkeys).LastOrDefault(x=>x[1]=='-'); Console.WriteLine(referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway);
String.Format在内部使用StringBuilder
,所以在逻辑上会导致这样的想法,即由于更多的开销,性能会稍差。 但是,简单的string连接是在另外两个string之间注入一个string的最快方法。 Rico Mariani在多年前的第一次性能测验中就certificate了这一点。 简单的事实是,串联…当string部分的数量是已知的(没有限制..你可以连接上千个部分…只要你知道总是1000部分)…总是比StringBuilder
或string更快。格式。 他们可以用一个内存分配执行一系列内存拷贝。 这是certificate
下面是一些String.Concat方法的实际代码,最终调用FillStringChecked,它使用指针来复制内存(通过Reflector提取):
public static string Concat(params string[] values) { int totalLength = 0; if (values == null) { throw new ArgumentNullException("values"); } string[] strArray = new string[values.Length]; for (int i = 0; i < values.Length; i++) { string str = values[i]; strArray[i] = (str == null) ? Empty : str; totalLength += strArray[i].Length; if (totalLength < 0) { throw new OutOfMemoryException(); } } return ConcatArray(strArray, totalLength); } public static string Concat(string str0, string str1, string str2, string str3) { if (((str0 == null) && (str1 == null)) && ((str2 == null) && (str3 == null))) { return Empty; } if (str0 == null) { str0 = Empty; } if (str1 == null) { str1 = Empty; } if (str2 == null) { str2 = Empty; } if (str3 == null) { str3 = Empty; } int length = ((str0.Length + str1.Length) + str2.Length) + str3.Length; string dest = FastAllocateString(length); FillStringChecked(dest, 0, str0); FillStringChecked(dest, str0.Length, str1); FillStringChecked(dest, str0.Length + str1.Length, str2); FillStringChecked(dest, (str0.Length + str1.Length) + str2.Length, str3); return dest; } private static string ConcatArray(string[] values, int totalLength) { string dest = FastAllocateString(totalLength); int destPos = 0; for (int i = 0; i < values.Length; i++) { FillStringChecked(dest, destPos, values[i]); destPos += values[i].Length; } return dest; } private static unsafe void FillStringChecked(string dest, int destPos, string src) { int length = src.Length; if (length > (dest.Length - destPos)) { throw new IndexOutOfRangeException(); } fixed (char* chRef = &dest.m_firstChar) { fixed (char* chRef2 = &src.m_firstChar) { wstrcpy(chRef + destPos, chRef2, length); } } }
那么:
string what = "cat"; string inthehat = "The " + what + " in the hat!";
请享用!
哦,最快的是:
string cat = "cat"; string s = "The " + cat + " in the hat";
这真的取决于你的使用模式。
string.Join
, string,Concat
和string.Format
之间的详细基准可以在这里find: String.Format不适用于强化日志logging
这真的取决于。 对于串联less的小string,追加string实际上更快。
String s = "String A" + "String B";
但是对于较大的string(非常大的string),使用StringBuilder会更高效。
在上面的两种情况下,我想注入一个或多个string到预定义的模板string的中间。
在这种情况下,我build议String.Format是最快的,因为它是为了确切的目的而devise的。
我build议不要这样做,因为String.Format不是为串联而devise的,而是devise用于格式化各种input(如date)的输出。
String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);