如何实现StringBuilder类?每次追加时都会在内部创建新的字符串对象吗?
答案 0 :(得分:53)
在.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中的更改(我刚才发现)。
答案 1 :(得分:27)
接受的答案错过了一英里的标记。 4.0中StringBuilder
的重大变化不是从不安全的string
变为char[]
- 事实上StringBuilder
现在实际上是一个链接列表StringBuilder
个实例。
这种改变的原因应该是显而易见的:现在永远不需要重新分配缓冲区(一个昂贵的操作,因为,除了分配更多的内存,你还必须从旧缓冲区复制所有内容到新的)。
这意味着调用ToString()
现在稍慢,因为需要计算最终字符串,但是现在执行大量Append()
操作显着更快。这符合StringBuilder
的典型用例:对Append()
的大量调用,然后只调用ToString()
。
您可以找到基准here。结论?新的链表StringBuilder
使用了更多的内存,但对于典型的用例来说明显更快。
答案 2 :(得分:7)
不是 - 它使用内部字符缓冲区。只有当缓冲区容量耗尽时,它才会分配新的缓冲区。追加操作将简单地添加到此缓冲区,当调用ToString()方法时将创建字符串对象 - 此后,由于每个传统的字符串连接将创建新字符串,因此它适用于许多字符串连接。您还可以指定字符串构建器的初始容量,如果您对它有粗略的了解以避免多次分配。
编辑:人们指出我的理解是错误的。 请忽略答案(我宁愿不删除它 - 它将证明我的无知: - )
答案 3 :(得分:3)
我做了一个小样本来演示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找到一些单元测试和完整的示例代码。
答案 4 :(得分: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[]
答案 5 :(得分:2)
如果你想看到一个可能的实现(类似于微软实现到v3.5的那个实现),你可以在github上看到the source of the Mono one。