StringBuilder类是如何实现的?在追加字符串的时候,它是否内部创建了新的字符串对象?
StringBuilder类是如何实现的?在追加字符串的时候,它是否内部创建了新的字符串对象?
.NET 2.0内部使用String类。在System命名空间之外,String是不可变的,因此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中的更改(我刚刚发现)。
Char
数组,而不是String
。 (也许这已经改变了?)@Brian - Fredrik Mörkstring
,现在似乎是在操作一个 char
数组。 - Fredrik Mörk接受的答案完全错了。StringBuilder在4.0中的重大改变不是从不安全的string类型到char[]类型的转变,而是事实上它现在是一个链表的StringBuilder实例。
这个改变的原因很明显:现在再也没有必要重新分配缓冲区(一项昂贵的操作,因为除了分配更多的内存外,您还必须将所有内容从旧缓冲区复制到新缓冲区)。
这意味着调用ToString()现在会稍微慢一些,因为最终字符串需要计算,但进行大量的Append()操作现在会显着快得多。这与StringBuilder的典型用例相符:大量调用Append(),然后跟随一个单独的调用ToString()。
您可以在这里找到基准测试结果。结论是,新的链表StringBuilder使用的内存略微增加,但对于典型用例来说速度显着更快。
编辑:人们指出我的理解是错误的。 请忽略答案(我宁愿不删除它 - 它将作为我无知的证明 :-)
string
实例。真的。 - Marc Gravellchar[]
,但如果没有注意到绳索结构(m_ChunkPrevious
),那就是一个无用的事实,而这才是真正的新东西。 - Jirka Hanika我制作了一个小示例,演示了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上找到。
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[]
。