是String.Format和StringBuilder一样有效


160

假设我在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);

如果是这样,为什么?

编辑

经过一些有趣的回答后,我意识到我可能应该对自己的询问更加清楚。我并不是在问哪个连接字符串更快,但是哪个一个字符串注入另一个更快。

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

对困惑感到抱歉


请保持开放状态,以便将来进行改进。
Mark Biek

4
在特殊情况下,最快的方法都不是这些:如果要替换的部件的大小与新部件的大小相等,则可以就地更改字符串。不幸的是,这需要反射或不安全的代码,并故意违反了字符串的不变性。这不是一个好习惯,但是如果速度是一个问题... :)
Abel

在上面给出的示例中,string s = "The "+cat+" in the hat";除非将其用于循环中,否则可能是最快的,在这种情况下,最快的将是StringBuilder 在循环外部进行初始化。
Surya Pratap

Answers:


146

注意:此答案是在.NET 2.0是当前版本时编写的。这可能不再适用于更高版本。

String.FormatStringBuilder内部使用:

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,它有助于提高代码的可读性。如果您怀疑性能瓶颈,请在代码上粘贴探查器,然后查看其实际位置。


8
杰里·迪克森(Jerry Dixon)页面上的基准测试存在的一个问题是,他从未调用.ToString()过该StringBuilder对象。在许多次迭代中,这段时间产生了很大的不同,这意味着他并没有完全将苹果与苹果进行比较。这就是他表现出如此出色的表现的原因,StringBuilder并且可能是他感到惊讶的原因。我只是重复进行基准测试以更正该错误,并获得了预期的结果:String +操作员最快,其次是StringBuilder,它String.Format提起了后部。
Ben Collins

5
6年后,情况不再如此。在Net4中,string.Format()创建并缓存一个可重复使用的StringBuilder实例,因此在某些测试案例中,它可能比StringBuilder快。我在下面的答案中添加了经过修订的基准测试(它仍然表示concat最快,并且在我的测试案例中,格式比StringBuilder慢10%)。
克里斯·卡罗尔

45

MSDN文档中

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


12

我运行了一些快速的性能基准测试,对于平均10次运行的100,000次操作,第一种方法(字符串生成器)花费的时间几乎是第二种方法(字符串格式)的一半。

因此,如果这种情况很少见,那就没关系了。但是,如果这是常见操作,则您可能需要使用第一种方法。


10

我希望String.Format变慢-它必须解析字符串,然后将其串联。

几个注意事项:

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

8

我认为在大多数情况下,这种清晰而不是效率的问题应该是您最大的担忧。除非您压碎成串的弦,或者为功率较低的移动设备制造东西,否则这可能不会大大降低您的运行速度。

我发现,在以相当线性的方式构建字符串的情况下,进行直接串联或使用StringBuilder是最佳选择。如果您要构建的大多数字符串是动态的,则建议这样做。由于很少的文本是静态的,所以最重要的是,很清楚每条动态文本放在哪里,以防将来需要更新。

另一方面,如果您谈论的是包含两个或三个变量的大量静态文本,即使效率稍低,我认为您从string.Format获得的清晰度也值得。本周早些时候,我不得不在4页文档的中央放置一点动态文本时使用了此功能。如果要整段地更新大块文本,则比必须更新连接在一起的三段文本要容易得多。


是! 在合理的情况下(例如,在格式化字符串时)使用String.Format。执行机械连接时,请使用字符串连接或StringBuilder。始终努力选择一种可以将您的意图传达给下一个维护者的方法。
罗布

8

如果仅仅是因为string.Format不能完全按照您的想法去做,那么这是6年后在Net45上重新运行的测试。

Concat仍然是最快的,但实际上相差不到30%。StringBuilder和Format相差仅5-10%。我几次运行测试得到20%的差异。

毫秒,一百万次迭代:

  • 串联:367
  • 每个键的新stringBuilder:452
  • 缓存的StringBuilder:419
  • 字符串格式: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);

2
我所说的“ string.Format并没有完全按照您的想法去做”,我的意思是在4.5源代码中它试图创建并重新使用一个缓存的StringBuilder实例。所以我在测试中加入了这种方法
克里斯·卡罗尔

6

String.Format在StringBuilder内部使用...从逻辑上讲,导致这样的想法,即由于更多的开销,它的性能会降低一点。但是,简单的字符串串联是在两个字符串之间注入一个字符串的最快方法……在很大程度上。多年前,Rico Mariani在他的第一个表演测验中就证明了这一点。一个简单的事实是,串联...当知道字符串部分的数量时(无限制StringBuilder.. 您可以串联一千个部分...只要知道它总是1000个部分)...总是比or 快。格式。它们可以通过单个内存分配和一系列内存副本来执行。是证明

这是一些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!";

请享用!


在Net4中,string.Format缓存并重用StringBuilder实例,因此在某些情况下可能会更快。
克里斯·卡罗尔

3

哦,最快的是:

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

否,字符串连接非常慢,因为在这种情况下,.NET在连接操作之间创建了字符串变量的额外副本,即:两个额外的副本以及赋值的最终副本。结果:与StringBuilder最初优化这种类型的编码相比,性能极差。
亚伯2009年

最快打字的可能是;)
UpTheCreek 2010年

2
@Abel:答案可能缺少细节,但是在此特定示例中,这种方法是最快的选择。编译器会将其转换为单个String.Concat()调用,因此用StringBuilder替换实际上会降低代码速度。
Dan C.

1
@Vaibhav是正确的:在这种情况下,串联是最快的。当然,除非重复很多次,或者在更大得多的字符串上进行操作,否则差异将是微不足道的。
Ben Collins

0

真的要看 对于带有很少串联的小字符串,仅附加字符串实际上更快。

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

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


0

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

在这种情况下,我建议使用String.Format最快,因为它是专门为该目的而设计的。



-1

我不建议这样做,因为String.Format不是为串联而设计的,所以它是为格式化各种输入(例如日期)的输出而设计的。

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.