在 C# 中,用于增大 StringBuilder 使用的内部缓冲区的策略随着时间的推移而变化。
有三种基本策略可以解决此问题,并且它们具有不同的性能特征。
第一个基本策略是:
- 创建字符数组
- 当您的空间不足时,为一些常量 k 创建一个包含 k 更多字符的新数组。
- 将旧数组复制到新数组,并孤立旧数组。
这种策略存在许多问题,其中最明显的是,如果正在构建的字符串非常大,那么它在时间上是O(n2)。假设 k 是一千个字符,最后一个字符串是一百万个字符。您最终将字符串重新分配到 1000、2000、3000、4000、...因此复制1000 + 2000 + 3000 + 4000 + ... + 999000个字符,这总共复制了大约5000亿个字符!
此策略具有很好的特性,即“浪费”的内存量以 k 为限。
在实践中,由于n平方问题,这种策略很少使用。
第二个基本策略是
- 创建数组
- 当您的空间不足时,为一些常量 k 创建一个字符数增加 k% 的新数组。
- 将旧数组复制到新数组,并孤立旧数组。
k%通常为100%;如果是这样,那么这被称为“满时加倍”策略。
这种策略具有很好的属性,即其摊销成本为O(n)。再次假设最后一个字符串是一百万个字符,你从一千个字符开始。您在1000,2000,4000,8000,...最终复制了1000 + 2000 + 4000 + 8000 ... + 512000个字符,这相当于复制了大约一百万个字符;好多了。
该策略具有这样的属性:无论您选择什么百分比,摊销成本都是线性的。
此策略有许多缺点,即有时复制操作非常昂贵,并且您可能会在未使用的内存中浪费高达最终字符串长度的 k%。
第三种策略是制作数组的链接列表,每个数组的大小为k。当现有数组溢出时,将分配一个新数组并将其追加到列表的末尾。
此策略具有很好的特性,即没有操作是特别昂贵的,总浪费的内存受 k 限制,并且您不需要能够定期在堆中找到大块。它的缺点是,最终将事物转换为字符串可能会很昂贵,因为链接列表中的数组可能具有较差的位置。
.NET 框架中的字符串生成器曾经使用双满策略;它现在使用块的链接列表策略。