如何实现StringBuilder类? 每次我们追加它是否在内部创build新的string对象?

如何实现StringBuilder类? 每次我们追加它是否在内部创build新的string对象?

在.NET 2.0中,它在内部使用String类。 StringSystem命名空间之外只是不可变的,所以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(我只是刚刚发现)的变化。

接受的答案错过了一英里的标志。 在4.0中对StringBuilder的重大改变不是从不安全的stringchar[] – 这是StringBuilder 现在实际上是StringBuilder实例的链接列表。


这种改变的原因应该是显而易见的:现在从来没有需要重新分配缓冲区(一个昂贵的操作,因为随着分配更多的内存,你也必须将所有的内容从旧的缓冲区复制到新的)

这意味着调用ToString()现在稍微慢一些,因为最后一个string需要计算,但是执行大量的Append()操作现在快得多。 这符合StringBuilder的典型用例:对Append()的大量调用,然后对ToString()进行一次调用。


你可以在这里find基准。 结论? 新的链接列表StringBuilder使用更多的内存,但对于典型的使用情况来说要快得多。

不是真的 – 它使用内部字符缓冲区。 只有当缓冲区容量耗尽时,才会分配新的缓冲区。 追加操作将简单地添加到此缓冲区中,当对其调用ToString()方法时,将创buildstring对象 – 从今以后,因为每个传统的string连接都会创build新的string,所以对于许多string连接是可取的。 您也可以指定string生成器的初始容量,如果您对此有粗略的想法以避免多次分配。

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

如果我在.NET 2上看.NET Reflector,那么我会find这样的:

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

所以这是一个变异的string实例…

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

如果你想看看其中一个可能的实现(这与微软实现版本v3.5相似),你可以在github上看到Mono的源代码 。

我已经做了一个小例子来演示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(); } } } 

此代码不是线程安全的,不会进行任何inputvalidation,也不会使用System.String的内部(不安全)魔法。 但是,它演示了StringBuilder类的思想。

有些unit testing和完整的示例代码可以在githubfind。