首页 文章

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

提问于
浏览
49

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

6 回答

  • 3

    如果你想看到一个可能的实现(类似于微软实现到v3.5的那个),你可以在github上看到the source of the Mono one .

  • 26

    在.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中的变化(我刚刚发现) .

  • 53

    接受的答案错过了一英里的标记 . 4.0中 StringBuilder 的重大变化不是从不安全的 string 变为 char[] - 事实是 StringBuildernow actually a linked-list of StringBuilder instances.


    这种变化的原因应该是显而易见的:现在永远不需要重新分配缓冲区(这是一项昂贵的操作,因为除了分配更多内存外,还必须将旧缓冲区中的所有内容复制到新缓冲区中) .

    这意味着调用 ToString() 现在稍慢,因为需要计算最终字符串,但是现在执行大量的 Append() 操作要快得多 . 这适用于 StringBuilder 的典型用例:大量调用 Append() ,然后调用 ToString() .


    你可以找到基准here . 结论?新的链表 StringBuilder 使用了更多的内存,但对于典型的用例来说明显更快 .

  • 7

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

    Edit :人们指出我的理解是错误的 . Please ignore the answer (我宁愿不删除它 - 它将证明我的无知:-)

  • 2

    我做了一个小样本来演示StringBuilder在.NET 4中是如何工作的 . Contract 是

    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找到 .

  • 2

    如果我在.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[]

相关问题