我清楚地记得从早期的.NET开始,在StringBuilder上调用ToString用于提供新的字符串对象(要返回)与StringBuilder使用的内部char缓冲区。这样,如果使用StringBuilder构造了一个巨大的字符串,则调用ToString不必复制它。
在这样做时,StringBuilder必须阻止对缓冲区的任何其他更改,因为它现在由不可变字符串使用。因此,StringBuilder将切换到“copy-on-change”,其中任何尝试的更改将首先创建一个新的缓冲区,将旧缓冲区的内容复制到它,然后才更改它。
我认为假设StringBuilder将用于构造字符串,然后转换为常规字符串并丢弃。对我来说似乎是一个合理的假设。
现在就是这样。我在文档中找不到任何提及。但我不确定它是否有记录。
所以我使用Reflector(.NET 4.0)查看了ToString的实现,在我看来它实际上是复制字符串,而不是仅仅共享缓冲区:
[SecuritySafeCritical]
public override unsafe string ToString()
{
string str = string.FastAllocateString(this.Length);
StringBuilder chunkPrevious = this;
fixed (char* str2 = ((char*) str))
{
char* chPtr = str2;
do
{
if (chunkPrevious.m_ChunkLength > 0)
{
char[] chunkChars = chunkPrevious.m_ChunkChars;
int chunkOffset = chunkPrevious.m_ChunkOffset;
int chunkLength = chunkPrevious.m_ChunkLength;
if ((((ulong) (chunkLength + chunkOffset)) > str.Length) || (chunkLength > chunkChars.Length))
{
throw new ArgumentOutOfRangeException("chunkLength", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
fixed (char* chRef = chunkChars)
{
string.wstrcpy(chPtr + chunkOffset, chRef, chunkLength);
}
}
chunkPrevious = chunkPrevious.m_ChunkPrevious;
}
while (chunkPrevious != null);
}
return str;
}
现在,正如我之前提到的,我清楚地记得在早期的情况下读到这种情况,如果是.NET。我甚至在此book中找到了提及。
我的问题是,这种行为被删除了吗?如果是这样,有谁知道为什么?这对我来说很有意义......
答案 0 :(得分:5)
是的,你没记错的。 StringBuilder.ToString
方法用于将内部缓冲区作为字符串返回,并将其标记为已使用,以便StringBuilder
的其他更改必须分配新缓冲区。
由于这是一个实现细节,因此文档中未提及。这就是为什么他们可以在不破坏类定义行为的任何内容的情况下更改底层实现。
从发布的代码中可以看出,不再有单个内部缓冲区,而是将字符存储在块中,ToString
方法将块组合成一个字符串。
实施中这种变化的原因很可能是他们收集了有关StringBuilder
类实际使用方式的信息,并得出结论:这种方法在平均情况和最差情况之间给出了更好的性能。
答案 1 :(得分:5)
是的,这已经完全重新设计用于.NET 4.0。它现在使用一根绳子,一个字符串构建器的链表来存储不断增长的内部缓冲区。当您无法正确猜出初始容量且文本量很大时,这是一个问题的解决方法。这会创建大量未使用的内部缓冲区副本,从而堵塞大对象堆。来自参考源的源代码中的此注释是相关的:
// We want to keep chunk arrays out of large object heap (< 85K bytes ~ 40K chars) to be sure.
// Making the maximum chunk size big means less allocation code called, but also more waste
// in unused characters and slower inserts / replaces (since you do need to slide characters over
// within a buffer).
internal const int MaxChunkSize = 8000;
答案 2 :(得分:2)
以下是Reflector中的StringBuilder.ToString
的.NET 1.1实现:
public override string ToString()
{
string stringValue = this.m_StringValue;
int currentThread = this.m_currentThread;
if ((currentThread != 0) && (currentThread != InternalGetCurrentThread()))
{
return string.InternalCopy(stringValue);
}
if ((2 * stringValue.Length) < stringValue.ArrayLength)
{
return string.InternalCopy(stringValue);
}
stringValue.ClearPostNullChar();
this.m_currentThread = 0;
return stringValue;
}
据我所知,在某些情况下它会返回字符串而不复制它。但是,我不认为StringBuilder
变得不可变。相反,如果您继续写入StringBuilder
,我认为它将使用copy-on-write。
答案 3 :(得分:0)
这很可能只是一个实现细节,而不是StringBuilder.ToString
提供的界面上的文档约束。您不确定是否曾记录过这一事实可能表明情况确实如此。
书籍通常会详细说明实施,以展示如何使用某些内容的一些见解,但大多数都会发出警告,说明实施可能会发生变化。
一个很好的例子,说明为什么不应该依赖实现细节。
我怀疑构建器变得不可变不是一个特性,而只是ToString
实现的副作用。
答案 4 :(得分:0)
我以前没见过,所以我的猜测是:StringBuilder
的内部存储看起来不再是简单的string
,而是一组“块”。 ToString
无法返回对此内部字符串的引用,因为它不再存在。
(版本4.0 StringBuilders现在是ropes吗?)