代码之家  ›  专栏  ›  技术社区  ›  Ram

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

  •  50
  • Ram  · 技术社区  · 15 年前

    6 回复  |  直到 6 年前
        1
  •  54
  •   Brian Rasmussen    14 年前

    在.NET2.0中,它使用 String 内部分类。 System StringBuilder

    字符串 已更改为使用 char[]

    在2.0中 字符串拼接

    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 使用

    编辑:更新了答案以反映.NET4中的变化(我只是刚刚发现)。

        2
  •  31
  •   BlueRaja - Danny Pflughoeft    13 年前

    公认的答案与标准差了一英里。重大变化 StringBuilder 在4.0中不是从一个不安全的 string char[] -事实是 字符串拼接 现在实际上是一个 字符串拼接 实例。


    (这是一个昂贵的操作,因为除了分配更多内存外,还必须将所有内容从旧缓冲区复制到新缓冲区) .

    ToString() 现在稍微慢一点,因为需要计算最后一个字符串,但是要做大量的 Append() 更快。这符合 :打了很多电话给 ,然后是对 .


    here . 结论呢?新的链表 字符串拼接 使用稍微多一些的内存,但是对于典型的用例来说要快得多。

        3
  •  7
  •   VinayC    15 年前

    不是真的-它使用内部字符缓冲区。只有当缓冲区容量耗尽时,它才会分配新的缓冲区。Append操作将简单地添加到此缓冲区,当调用ToString()方法时将创建string对象—从今往后,建议对许多字符串进行串联,因为每个传统的字符串concat操作将创建新的字符串。如果您对字符串生成器有粗略的了解,还可以指定其初始容量,以避免多次分配。

    编辑 :人们指出我的理解是错误的。

        4
  •  3
  •   oleksii    13 年前

    我制作了一个小示例来演示StringBuilder如何在.NET4中工作。合同是

    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();
            }
        }
    }
    

    此代码不是线程安全的,不进行任何输入验证,也不使用系统字符串. 不过,它确实演示了StringBuilder类背后的思想。

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

        5
  •  2
  •   Richard    15 年前

    如果我在.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
  •  2
  •   Julien Roncaglia    15 年前

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