【问题标题】:How the StringBuilder class is implemented? Does it internally create new string objects each time we append?StringBuilder 类是如何实现的?每次我们追加时它会在内部创建新的字符串对象吗?
【发布时间】:2010-08-25 10:30:23
【问题描述】:

StringBuilder 类是如何实现的?每次我们追加时它会在内部创建新的字符串对象吗?

【问题讨论】:

  • +1 我也从这个问题中学到了一些新东西 :)
  • @Brian Rasmussen 等待 Jon Skeet 的回答。我敢打赌,它会是巨大的,充满了新的东西要学;)
  • 只是猜测。它被分块以避免大字符串的 LOH。

标签: c# .net string stringbuilder


【解决方案1】:

在 .NET 2.0 中,它在内部使用 String 类。 String 仅在 System 命名空间之外是不可变的,所以 StringBuilder 可以做到这一点。

在 .NET 4.0 中,String 已更改为使用 char[]

在 2.0 中 StringBuilder 看起来像这样

public sealed class StringBuilder : ISerializable
{
    // Fields
    private const string CapacityField = "Capacity";
    internal const int DefaultCapacity = 0x10;
    internal IntPtr m_currentThread;
    internal int m_MaxCapacity;
    internal volatile string m_StringValue; // HERE ----------------------
    private const string MaxCapacityField = "m_MaxCapacity";
    private const string StringValueField = "m_StringValue";
    private const string ThreadIDField = "m_currentThread";

但在 4.0 中它看起来像这样:

public sealed class StringBuilder : ISerializable
{
    // Fields
    private const string CapacityField = "Capacity";
    internal const int DefaultCapacity = 0x10;
    internal char[] m_ChunkChars; // HERE --------------------------------
    internal int m_ChunkLength;
    internal int m_ChunkOffset;
    internal StringBuilder m_ChunkPrevious;
    internal int m_MaxCapacity;
    private const string MaxCapacityField = "m_MaxCapacity";
    internal const int MaxChunkSize = 0x1f40;
    private const string StringValueField = "m_StringValue";
    private const string ThreadIDField = "m_currentThread";

很明显,它从使用string 更改为使用char[]

编辑:更新答案以反映 .NET 4 中的变化(我刚刚发现)。

【讨论】:

  • 不知道..我想我会做一些反射器魔术来满足我的好奇心:)
  • @Brian:据我所知,它在内部拥有一个Char 数组,而不是String(至少在.NET 4 中,也许这已经改变了?)
  • @Fredrik - 在 MS 实现中,它确实是一个被突变的 string
  • @Marc:这让我很好奇,所以我检查了 Reflector;看起来这已经改变了。之前是string,现在看来是char 数组被操纵了。
【解决方案2】:

接受的答案与分数相差一英里。 4.0 中StringBuilder 的重大变化不是从不安全的string 更改为char[] - 事实上StringBuilder现在实际上是StringBuilder 实例的链接列表。


这个改变的原因应该很明显:现在不需要重新分配缓冲区(这是一个昂贵的操作,因为除了分配更多内存之外,您还必须从旧缓冲区复制所有内容到新的)

这意味着调用 ToString() 现在会稍微慢一些,因为需要计算最终的字符串,但是现在执行大量的 Append() 操作现在明显快了。这符合StringBuilder 的典型用例:多次调用Append(),然后调用一次ToString()


您可以找到基准here。结论?新的链表StringBuilder 使用的内存稍微多一些,但对于典型用例来说要快得多。

【讨论】:

【解决方案3】:

并非如此 - 它使用内部字符缓冲区。只有当缓冲区容量耗尽时,它才会分配新的缓冲区。附加操作将简单地添加到此缓冲区,当对其调用 ToString() 方法时将创建字符串对象 - 此后,它适用于许多字符串连接,因为每个传统的字符串连接操作都会创建新字符串。如果您对它有粗略的了解,也可以为字符串生成器指定初始容量以避免多次分配。

编辑:人们指出我的理解是错误的。 请忽略答案(我宁愿不删除它 - 这将证明我的无知:-)

【讨论】:

  • 它的行为好像它是一个字符缓冲区,但它实际上是一个变异的string 实例。诚实。
  • 谢谢 Marc - 我的印象是它使用字符缓冲区。这意味着它将有一些本地实现来改变字符串对象。
  • 当然,但它是一个核心框架类。它可以访问本机实现。
  • apols,看起来(此页面上的以前的 cmets)这在 .NET 4 中发生了变化。
  • 不管怎样,让我们​​说清楚,这个答案是.NET 4.0 暂时在这里唯一正确的答案,不应该把自己贬低为“不正确”。其他一些答案发现了char[],但这是一个无用的事实,没有注意到绳索结构(m_ChunkPrevious),这是真正的新事物。
【解决方案4】:

我制作了一个小示例来演示 StringBuilder 在 .NET 4 中的工作原理。合同是

public interface ISimpleStringBuilder
{
    ISimpleStringBuilder Append(string value);
    ISimpleStringBuilder Clear();
    int Lenght { get; }
    int Capacity { get; }
}

这是一个非常基本的实现

public class SimpleStringBuilder : ISimpleStringBuilder
{
    public const int DefaultCapacity = 32;

    private char[] _internalBuffer;

    public int Lenght { get; private set; }
    public int Capacity { get; private set; }

    public SimpleStringBuilder(int capacity)
    {
        Capacity = capacity;
        _internalBuffer = new char[capacity];
        Lenght = 0;
    }

    public SimpleStringBuilder() : this(DefaultCapacity) { }

    public ISimpleStringBuilder Append(string value)
    {
        char[] data = value.ToCharArray();

        //check if space is available for additional data
        InternalEnsureCapacity(data.Length);

        foreach (char t in data)
        {
            _internalBuffer[Lenght] = t;
            Lenght++;
        }

        return this;
    }

    public ISimpleStringBuilder Clear()
    {
        _internalBuffer = new char[Capacity];
        Lenght = 0;
        return this;
    }

    public override string ToString()
    {
        //use only non-null ('\0') characters
        var tmp = new char[Lenght];
        for (int i = 0; i < Lenght; i++)
        {
            tmp[i] = _internalBuffer[i];
        }
        return new string(tmp);
    }

    private void InternalExpandBuffer()
    {
        //double capacity by default
        Capacity *= 2;

        //copy to new array
        var tmpBuffer = new char[Capacity];
        for (int i = 0; i < _internalBuffer.Length; i++)
        {
            char c = _internalBuffer[i];
            tmpBuffer[i] = c;
        }
        _internalBuffer = tmpBuffer;
    }

    private void InternalEnsureCapacity(int additionalLenghtRequired)
    {
        while (Lenght + additionalLenghtRequired > Capacity)
        {
            //not enough space in the current buffer    
            //double capacity
            InternalExpandBuffer();
        }
    }
}

此代码不是线程安全的,不会进行任何输入验证,也没有使用 System.String 的内部(不安全)魔法。然而,它确实展示了 StringBuilder 类背后的想法。

一些单元测试和完整的示例代码可以在github找到。

【讨论】:

    【解决方案5】:

    如果我在 .NET 2 上查看 .NET Reflector,我会发现:

    public StringBuilder Append(string value)
    {
        if (value != null)
        {
            string stringValue = this.m_StringValue;
            IntPtr currentThread = Thread.InternalGetCurrentThread();
            if (this.m_currentThread != currentThread)
            {
                stringValue = string.GetStringForStringBuilder(stringValue, stringValue.Capacity);
            }
            int length = stringValue.Length;
            int requiredLength = length + value.Length;
            if (this.NeedsAllocation(stringValue, requiredLength))
            {
                string newString = this.GetNewString(stringValue, requiredLength);
                newString.AppendInPlace(value, length);
                this.ReplaceString(currentThread, newString);
            }
            else
            {
                stringValue.AppendInPlace(value, length);
                this.ReplaceString(currentThread, stringValue);
            }
        }
        return this;
    }
    

    所以它是一个变异的字符串实例......

    编辑除了在 .NET 4 中它是 char[]

    【讨论】:

      【解决方案6】:

      如果您想查看其中一种可能的实现(类似于 microsoft 实现 v3.5 的版本),您可以在 github 上查看 the source of the Mono one

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-09-02
        • 2021-06-07
        • 1970-01-01
        • 1970-01-01
        • 2018-12-09
        • 2014-12-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多