假设我在C#中有一个stringbuilder可以做到这一点:
1 2 3 4
| StringBuilder sb = new StringBuilder();
string cat ="cat";
sb.Append("the").Append(cat).(" in the hat");
string s = sb.ToString(); |
是否会比拥有以下产品效率更高或更有效?
1 2
| string cat ="cat";
string s = String.Format("The {0} in the hat", cat); |
如果是这样,为什么?
编辑
经过一些有趣的回答后,我意识到我可能应该对自己的询问更加清楚。 我并不是在问哪个连接字符串更快,但是哪个字符串注入另一个更快。
在以上两种情况下,我都希望将一个或多个字符串注入到预定义模板字符串的中间。
对困惑感到抱歉
注意:此答案是在.NET 2.0是当前版本时编写的。这可能不再适用于更高版本。
String.Format在内部使用StringBuilder:
1 2 3 4 5 6 7 8 9 10 11
| 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.Append()是否比StringBuilder.AppendFormat()快"?
如果没有基准测试,我可能会说上面的代码示例使用.Append()可以更快地运行。但这是一个猜测,请尝试对这两者进行基准测试和/或性能分析以进行适当的比较。
这一章杰里·迪克森(Jerry Dixon)做了一些基准测试:
http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm
更新:
遗憾的是,以上链接自此消失了。但是,"返回机器"上仍然有一个副本:
http://web.archive.org/web/20090417100252/http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm
归根结底,这取决于是否要重复调用字符串格式,即您是否对100兆字节的文本进行了认真的文本处理,还是当用户一次又一次单击按钮时调用它。除非您要执行大量的批处理工作,否则我将坚持使用String.Format,它有助于提高代码的可读性。如果您怀疑性能瓶颈,请在代码上粘贴探查器,然后查看其实际位置。
从MSDN文档中:
The performance of a concatenation operation for a String or StringBuilder object depends on how often a memory allocation occurs. A String concatenation operation always allocates memory, whereas a StringBuilder concatenation operation only allocates memory if the StringBuilder object buffer is too small to accommodate the new data. Consequently, the String class is preferable for a concatenation operation if a fixed number of String objects are concatenated. In that case, the individual concatenation operations might even be combined into a single operation by the compiler. A StringBuilder object is preferable for a concatenation operation if an arbitrary number of strings are concatenated; for example, if a loop concatenates a random number of strings of user input.
我运行了一些快速的性能基准测试,对于10次运行平均可以进行100,000次操作,第一种方法(String Builder)花费的时间几乎是第二种方法(String Format)的一半。
因此,如果这种情况很少见,那就没关系了。但是,如果这是常见操作,则您可能需要使用第一种方法。
我希望String.Format变慢-它必须解析字符串,然后将其串联。
几个注意事项:
-
格式是在专业应用程序中使用用户可见的字符串的方式;这避免了本地化错误
-
如果您事先知道结果字符串的长度,请使用StringBuilder(Int32)构造函数预定义容量
如果仅仅是因为string.Format不能完全按照您的想法去做,这是6年后在Net45上重新运行的测试。
Concat仍然是最快的,但实际上相差不到30%。 StringBuilder和Format相差仅5-10%。我几次运行测试得到20%的差异。
毫秒,一百万次迭代:
-
串联:367
-
每个键的新stringBuilder:452
-
缓存的StringBuilder:419
-
字符串格式:475
我得到的教训是,性能差异是微不足道的,因此它不应该阻止您编写最简单的可读代码。对于我的钱来说,哪一个经常但并非总是a + b + c。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| 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); |
我认为在大多数情况下,这种清晰而不是效率的问题应该是您最大的担忧。除非您要压碎成串的弦,或者为功率较低的移动设备制造东西,否则这可能不会大大降低您的运行速度。
我发现,在以相当线性的方式构建字符串的情况下,进行直接串联或使用StringBuilder是最佳选择。如果您要构建的大多数字符串是动态的,则建议这样做。由于很少的文本是静态的,因此最重要的是,很清楚将每条动态文本放在哪里,以防将来需要更新。
另一方面,如果您谈论的是包含两个或三个变量的大量静态文本,即使效率稍低,我认为您从string.Format获得的清晰度也值得。本周早些时候,我不得不在4页文档的中央放置一点动态文本时使用了此功能。如果将一大段文本更新为一件,则比必须更新连接在一起的三段文本要容易得多。
String.Format在内部使用StringBuilder……从逻辑上讲,这会导致由于开销更大而导致性能降低的想法。但是,简单的字符串连接是在两个字符串之间注入一个字符串的最快方法……在很大程度上。多年前,Rico Mariani在他的第一个表演测验中就证明了这一证据。一个简单的事实是串联...当知道字符串部分的数量时(无限制..您可以串联一千个部分...只要知道它始终为1000个部分)...总是比StringBuilder快或String.Format。它们可以通过单个内存分配和一系列内存副本来执行。这是证明
这是一些String.Concat方法的实际代码,这些方法最终调用FillStringChecked,该方法使用指针复制内存(通过Reflector提取):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| 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);
}
}
} |
因此:
1 2
| string what ="cat";
string inthehat ="The" + what +" in the hat!"; |
请享用!
哦,最快的是:
1 2
| string cat ="cat";
string s ="The" + cat +" in the hat"; |
这实际上取决于您的使用方式。
可以在以下位置找到string.Join,string,Concat和string.Format之间的详细基准:String.Format不适合密集日志记录
In both cases above I want to inject one or more strings into the middle of a predefined template string.
在这种情况下,我建议使用String.Format最快,因为它是专门为该目的而设计的。
真的要看对于带有少量串联的小字符串,仅附加字符串实际上会更快。
1
| String s ="String A" +"String B"; |
但是对于较大的字符串(非常大的字符串),使用StringBuilder效率更高。
我不建议这样做,因为String.Format不是为串联而设计的,所以它是为格式化各种输入(例如日期)的输出而设计的。
1
| String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today); |