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

使用StringBuilder Remove方法比在循环中创建新的StringBuilder更有效吗?

如何解决《使用StringBuilderRemove方法比在循环中创建新的StringBuilder更有效吗?》经验,为你挑选了2个好方法。

在C#中,内存效率更高:选项#1还是选项#2?

public void TestStringBuilder()
{
    //potentially a collection with several hundred items:
    string[] outputStrings = new string[] { "test1", "test2", "test3" };

    //Option #1
    StringBuilder formattedOutput = new StringBuilder();
    foreach (string outputString in outputStrings)
    {
        formattedOutput.Append("prefix ");
        formattedOutput.Append(outputString);
        formattedOutput.Append(" postfix");

        string output = formattedOutput.ToString();
        ExistingOutputMethodThatOnlyTakesAString(output);

        //Clear existing string to make ready for next iteration:
        formattedOutput.Remove(0, output.Length);
    }

    //Option #2
    foreach (string outputString in outputStrings)
    {
        StringBuilder formattedOutputInsideALoop = new StringBuilder();

        formattedOutputInsideALoop.Append("prefix ");
        formattedOutputInsideALoop.Append(outputString);
        formattedOutputInsideALoop.Append(" postfix");

        ExistingOutputMethodThatOnlyTakesAString(
           formattedOutputInsideALoop.ToString());
    }
}

private void ExistingOutputMethodThatOnlyTakesAString(string output)
{
    //This method actually writes out to a file.
    System.Console.WriteLine(output);
}

Sixto Saez.. 7

几个答案温和地暗示我脱下我的笨蛋并自己弄明白,所以下面是我的结果.我认为这种情绪通常与这个网站的内容相悖,但如果你想做正确的事情,你可能会做.... :)

我修改了选项#1以利用@Ty建议使用StringBuilder.Length = 0而不是Remove方法.这使得两个选项的代码更相似.现在两个不同之处在于StringBuilder的构造函数是在循环中还是在循环之外,而选项#1现在使用Length方法来清除StringBuilder.这两个选项都设置为在具有100,000个元素的outputStrings数组上运行,以使垃圾收集器完成一些工作.

几个答案提供了一些提示,以查看各种PerfMon计数器等,并使用结果选择一个选项.我做了一些研究,最后使用了我工作的Visual Studio Team Systems Developer版本的内置Performance Explorer.我找到了一个多部分系列的第二篇博客文章,解释了如何在这里设置它.基本上,您连接单元测试以指向您要分析的代码; 通过向导和一些配置; 并启动单元测试分析.我启用了.NET对象分配和生命周期指标.分析的结果很难为这个答案格式化,所以我把它们放在最后.如果您将文本复制并粘贴到Excel中并稍微按摩它们,它们将是可读的.

选项#1的内存效率最高,因为它使垃圾收集器的工作量减少了一半,并且它将一半的内存和实例分配给StringBuilder对象而不是选项#2.对于日常编码,选择#2选项非常好.

如果您还在阅读,我问了这个问题,因为选项#2将使经验C/C++开发人员的内存泄漏检测器变得暴躁.如果在重新分配之前未释放StringBuilder实例,则会发生巨大的内存泄漏.当然,我们C#开发人员并不担心这些事情(直到他们跳起来咬我们).谢谢大家!!


ClassName   Instances   TotalBytesAllocated Gen0_InstancesCollected Gen0BytesCollected  Gen1InstancesCollected  Gen1BytesCollected
=======Option #1                    
System.Text.StringBuilder   100,001 2,000,020   100,016 2,000,320   2   40
System.String   301,020 32,587,168  201,147 11,165,268  3   246
System.Char[]   200,000 8,977,780   200,022 8,979,678   2   90
System.String[] 1   400,016 26  1,512   0   0
System.Int32    100,000 1,200,000   100,061 1,200,732   2   24
System.Object[] 100,000 2,000,000   100,070 2,004,092   2   40
======Option #2                 
System.Text.StringBuilder   200,000 4,000,000   200,011 4,000,220   4   80
System.String   401,018 37,587,036  301,127 16,164,318  3   214
System.Char[]   200,000 9,377,780   200,024 9,379,768   0   0
System.String[] 1   400,016 20  1,208   0   0
System.Int32    100,000 1,200,000   100,051 1,200,612   1   12
System.Object[] 100,000 2,000,000   100,058 2,003,004   1   20


Jon Skeet.. 6

选项2应该(我相信)实际上胜过选项1.调用Remove"强制"StringBuilder 的行为获取它已经返回的字符串的副本.该字符串在StringBuilder中实际上是可变的,并且除非需要,否则StringBuilder不会获取副本.使用选项1,它在基本清除阵列之前复制 - 使用选项2不需要复制.

选项2的唯一缺点是,如果字符串最终变长,则在附加时会有多个副本 - 而选项1保留缓冲区的原始大小.但是,如果情况确实如此,请指定初始容量以避免额外复制.(在您的示例代码中,字符串最终将大于默认的16个字符 - 使用容量为32来初始化它将减少所需的额外字符串.)

然而,除了性能之外,选项2更简洁.



1> Sixto Saez..:

几个答案温和地暗示我脱下我的笨蛋并自己弄明白,所以下面是我的结果.我认为这种情绪通常与这个网站的内容相悖,但如果你想做正确的事情,你可能会做.... :)

我修改了选项#1以利用@Ty建议使用StringBuilder.Length = 0而不是Remove方法.这使得两个选项的代码更相似.现在两个不同之处在于StringBuilder的构造函数是在循环中还是在循环之外,而选项#1现在使用Length方法来清除StringBuilder.这两个选项都设置为在具有100,000个元素的outputStrings数组上运行,以使垃圾收集器完成一些工作.

几个答案提供了一些提示,以查看各种PerfMon计数器等,并使用结果选择一个选项.我做了一些研究,最后使用了我工作的Visual Studio Team Systems Developer版本的内置Performance Explorer.我找到了一个多部分系列的第二篇博客文章,解释了如何在这里设置它.基本上,您连接单元测试以指向您要分析的代码; 通过向导和一些配置; 并启动单元测试分析.我启用了.NET对象分配和生命周期指标.分析的结果很难为这个答案格式化,所以我把它们放在最后.如果您将文本复制并粘贴到Excel中并稍微按摩它们,它们将是可读的.

选项#1的内存效率最高,因为它使垃圾收集器的工作量减少了一半,并且它将一半的内存和实例分配给StringBuilder对象而不是选项#2.对于日常编码,选择#2选项非常好.

如果您还在阅读,我问了这个问题,因为选项#2将使经验C/C++开发人员的内存泄漏检测器变得暴躁.如果在重新分配之前未释放StringBuilder实例,则会发生巨大的内存泄漏.当然,我们C#开发人员并不担心这些事情(直到他们跳起来咬我们).谢谢大家!!


ClassName   Instances   TotalBytesAllocated Gen0_InstancesCollected Gen0BytesCollected  Gen1InstancesCollected  Gen1BytesCollected
=======Option #1                    
System.Text.StringBuilder   100,001 2,000,020   100,016 2,000,320   2   40
System.String   301,020 32,587,168  201,147 11,165,268  3   246
System.Char[]   200,000 8,977,780   200,022 8,979,678   2   90
System.String[] 1   400,016 26  1,512   0   0
System.Int32    100,000 1,200,000   100,061 1,200,732   2   24
System.Object[] 100,000 2,000,000   100,070 2,004,092   2   40
======Option #2                 
System.Text.StringBuilder   200,000 4,000,000   200,011 4,000,220   4   80
System.String   401,018 37,587,036  301,127 16,164,318  3   214
System.Char[]   200,000 9,377,780   200,024 9,379,768   0   0
System.String[] 1   400,016 20  1,208   0   0
System.Int32    100,000 1,200,000   100,051 1,200,612   1   12
System.Object[] 100,000 2,000,000   100,058 2,003,004   1   20



2> Jon Skeet..:

选项2应该(我相信)实际上胜过选项1.调用Remove"强制"StringBuilder 的行为获取它已经返回的字符串的副本.该字符串在StringBuilder中实际上是可变的,并且除非需要,否则StringBuilder不会获取副本.使用选项1,它在基本清除阵列之前复制 - 使用选项2不需要复制.

选项2的唯一缺点是,如果字符串最终变长,则在附加时会有多个副本 - 而选项1保留缓冲区的原始大小.但是,如果情况确实如此,请指定初始容量以避免额外复制.(在您的示例代码中,字符串最终将大于默认的16个字符 - 使用容量为32来初始化它将减少所需的额外字符串.)

然而,除了性能之外,选项2更简洁.

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