String.Format 是否与 StringBuilder 一样有效

假设我在 C # 中有一个字符串构造器,它可以这样做:

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);

如果是这样,为什么?

编辑

在一些有趣的回答之后,我意识到我可能应该在我所问的问题上更清楚一些。我并没有过多地要求哪个在串接一个字符串时更快,但是在 注射时一个字符串到另一个字符串更快。

在上述两种情况下,我都想将一个或多个字符串插入到预定义模板字符串的中间。

抱歉给你添麻烦了

71424 次浏览

我建议不要,因为 String。设计格式不是为了串联,而是为了格式化各种输入(如日期)的输出。

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);

实际上要看情况,对于很少串联的小字符串,实际上仅仅添加字符串就会更快。

String s = "String A" + "String B";

但是对于更大的字符串(非常非常大的字符串) ,使用 StringBuilder 会更有效率。

来自 MSDN 文档:

String 或 StringBuilder 对象的串联操作的性能取决于内存分配发生的频率。String 连接操作总是分配内存,而 StringBuilder 连接操作只有在 StringBuilder 对象缓冲区太小而无法容纳新数据时才分配内存。因此,如果连接了固定数量的 String 对象,那么 String 类对于连接操作更可取。在这种情况下,编译器甚至可以将各个连接操作合并为一个操作。如果串联操作使用任意数量的字符串,那么 StringBuilder 对象更可取; 例如,如果循环串联用户输入的随机数量的字符串。

我认为在大多数情况下,像这样的清晰度,而不是效率,应该是你最大的关注。除非你正在把成吨的绳子压在一起,或者正在为一个低功耗的移动设备做一些东西,否则这可能不会对你的跑步速度造成太大的影响。

我发现,在以相当线性的方式构建字符串的情况下,直接连接或使用 StringBuilder 是最佳选择。如果您正在构建的大部分字符串是动态的,我建议使用这种方法。因为很少有文本是静态的,所以最重要的是要清楚每一段动态文本放在哪里,以防将来需要更新。

另一方面,如果你谈论的是一大块包含两个或三个变量的静态文本,即使它的效率有点低,我认为你从字符串中获得的清晰度。格式使它值得。本周早些时候,当我不得不在一个4页的文档中间放置一段动态文本时,我使用了这种方法。如果一大块文本是整块的,那么更新它就会比更新连接在一起的三块文本要容易得多。

注意: 这个答案是在.NET 2.0是当前版本时写的,这可能不再适用于以后的版本。

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();
}

上面的代码是来自 mscolib 的一个片段,所以问题变成了“ 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 吧。格式,它有助于代码的可读性。如果您怀疑 perf 瓶颈,那么在代码上粘贴一个分析器,看看它到底在哪里。

在上述两种情况下,我都想将一个或多个字符串插入到预定义模板字符串的中间。

在这种情况下,我建议 String.Format 是最快的,因为它的设计就是为了这个目的。

我预计 字符串,格式会慢一些——它必须解析字符串并且 那么连接它。

一些注意事项:

  • 在专业应用程序中,格式 是获得用户可见字符串的方法; 这避免了本地化错误
  • 如果事先知道结果字符串的长度,请使用 StringBuilder (Int32)构造函数预先定义容量

我运行了一些快速性能基准测试,对于平均超过10次运行的100,000个操作,第一个方法(String Builder)几乎占用了第二个方法(String Format)的一半时间。

所以,如果这种情况不常发生,也没关系。但是如果这是一个常见的操作,那么您可能需要使用第一个方法。

哦,还有,最快的可能是:

string cat = "cat";
string s = "The " + cat + " in the hat";

String.Format在内部使用 StringBuilder,因此逻辑上导致这样的想法: 由于更多的开销,它的性能会有所下降。然而,在很大程度上,简单的字符串串联是在两个字符串之间注入一个字符串的最快方法。这个证据被 Rico Mariani 在几年前的第一次表演测验中证明了。简单的事实是,当串联的字符串部分的数量已知时(没有限制ーー你可以串联1000个部分,只要你知道它总是1000个部分) ,串联总是比 StringBuilderString.Format快。它们可以通过一个内存分配和一系列内存副本来执行。给你就是证据。

下面是一些 String.Concat方法的实际代码,这些方法最终调用 FillStringChecked,它使用指针来复制内存(通过 Refector 提取) :

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.Joinstring,Concatstring.Format之间的详细基准:。格式不适用于密集测井

如果只是因为字符串。Format 并不完全像您想的那样,这里是6年后在 Net45上重新运行的测试。

联合仍然是最快的,但实际上它的差距不到30% 。StringBuilder 和 Format 的差异仅为5-10% 。我做过几次20% 的测试。

毫秒,一百万次迭代:

  • 级联: 367
  • 每个键新建一个 stringBuilder: 452
  • 缓存 StringBuilder: 419
  • 格式: 475

我从中得到的教训是,性能差异是微不足道的,因此它不应该阻止您尽可能编写最简单的可读代码。在我看来,这通常是 a + b + c,但并不总是 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);