当前位置:  开发笔记 > 编程语言 > 正文

StringBuilder.Append与StringBuilder.AppendFormat

如何解决《StringBuilder.Append与StringBuilder.AppendFormat》经验,为你挑选了4个好方法。

我想知道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);

casperOne.. 41

这是不可能的说,不知道的大小string1string2.

通过调用AppendFormat,它将在给定格式字符串的长度和将要插入的字符串的情况下预分配缓冲区,然后连接所有内容并将其插入缓冲区.对于非常大的字符串,这将比Append可能导致缓冲区多次扩展的单独调用更有利.

但是,三次调用Append可能会或可能不会触发缓冲区的增长,并且每次调用都会执行该检查.如果字符串足够小并且没有触发缓冲区扩展,那么它将比调用更快,AppendFormat因为它不必解析格式字符串以找出替换位置.

确定答案需要更多数据

应该注意的是,Concat关于在String课堂上使用静态方法的讨论很少(Jon的答案使用了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在一次测试的循环的每次迭代中创建一个新的,然后你运行下一个循环相同次数的测试,创建额外的 1,000,000个StringBuilder实例,GC很可能会在第二次测试中介入并阻碍其时间安排.



1> casperOne..:

这是不可能的说,不知道的大小string1string2.

通过调用AppendFormat,它将在给定格式字符串的长度和将要插入的字符串的情况下预分配缓冲区,然后连接所有内容并将其插入缓冲区.对于非常大的字符串,这将比Append可能导致缓冲区多次扩展的单独调用更有利.

但是,三次调用Append可能会或可能不会触发缓冲区的增长,并且每次调用都会执行该检查.如果字符串足够小并且没有触发缓冲区扩展,那么它将比调用更快,AppendFormat因为它不必解析格式字符串以找出替换位置.

确定答案需要更多数据

应该注意的是,Concat关于在String课堂上使用静态方法的讨论很少(Jon的答案使用了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在一次测试的循环的每次迭代中创建一个新的,然后你运行下一个循环相同次数的测试,创建额外的 1,000,000个StringBuilder实例,GC很可能会在第二次测试中介入并阻碍其时间安排.



2> John Rasch..:

casperOne是对的.一旦达到某个阈值,该Append()方法就会慢于AppendFormat().以下是每种方法100,000次迭代的不同长度和经过的时间间隔:

长度:1

Append()       - 50900
AppendFormat() - 126826

长度:1000

Append()       - 1241938
AppendFormat() - 1337396

长度:10,000

Append()       - 12482051
AppendFormat() - 12740862

长度:20,000

Append()       - 61029875
AppendFormat() - 60483914

当引入长度接近20,000的字符串时,该AppendFormat()函数将略微超越Append().

为什么会这样?请参阅casperOne的回答.

编辑:

我在Release配置下单独重新测试每个测试并更新结果.



3> Jon Skeet..:

casperOne完全准确,取决于数据.但是,假设您将此作为第三方使用的类库编写 - 您会使用哪个?

一种选择是充分利用两个世界 - 计算出你实际需要附加多少数据,然后使用StringBuilder.EnsureCapacity来确保我们只需要一个缓冲区调整大小.

如果我不是烦恼,我会使用Appendx3 - 似乎"更有可能"更快,因为在每个调用中解析字符串格式标记显然是make-work.

请注意,我已经向BCL团队询问了一种"缓存格式化程序",我们可以使用格式字符串创建,然后重复使用.框架必须在每次使用时解析格式字符串,这很疯狂.

编辑:好的,我在某种程度上编辑了John的代码以获得灵活性,并添加了一个"AppendWithCapacity",它首先确定了必要的容量.以下是不同长度的结果 - 对于长度1,我使用了1,000,000次迭代; 对于所有其他长度,我使用了100,000.(这只是为了获得合理的运行时间.)所有时间都是毫安.

不幸的是,桌子并没有真正起作用.长度分别为1,1000,10000,20000

时报:

追加:162,475,7997,17970

AppendFormat:392,499,8541,18993

AppendWithCapacity:139,189,1558,3085

事实上,我从未见过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 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);
    }
}



4> Andrew Hare..:

Append在大多数情况下会更快,因为该方法有许多重载,允许编译器调用正确的方法.由于您使用StringsStringBuilder可使用String的过载Append.

AppendFormat取一个String然后一个Object[]意味着必须解析格式Object,并且数组中的每个都必须ToString'd在它被添加到StringBuilder's内部数组之前.

注意:对于casperOne的观点 - 如果没有更多数据,很难给出确切的答案.

推荐阅读
罗文彬2502852027
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有