StringBuilder
如何运作?
它内部有什么作用?它使用不安全的代码吗?为什么它如此之快(与+
运算符相比)?
使用+运算符构建字符串时:
string s = "01";
s += "02";
s += "03";
s += "04";
然后在第一个连接中,我们创建一个长度为4的新字符串并将“01”和“02”复制到其中 - 复制四个字符。在第二个连接中,我们创建一个长度为6的新字符串并将“0102”和“03”复制到其中 - 复制了六个字符。在第三个concat上,我们创建一个长度为8的字符串并将“010203”和“04”复制到其中 - 复制了8个字符。到目前为止,这个8个字符的字符串总共复制了4 + 6 + 8 = 18个字符。继续。
...
s += "99";
在第98次连续中,我们制作了一个长度为198的字符串,并将“010203 ... 98”和“99”复制到其中。这给了我们总共4 + 6 + 8 + ... + 198 =很多,为了使这个198字符串。
字符串生成器不会完成所有复制。相反,它维护一个希望比最终字符串更大的可变数组,并根据需要将新内容填充到数组中。
当猜测错误并且数组变满时会发生什么?有两种策略。在以前版本的框架中,字符串构建器在数组填满时重新分配并复制了数组,并将其大小加倍。在新实现中,字符串构建器维护一个相对较小的数组的链接列表,并在旧的数组变满时将新数组附加到列表的末尾。
此外,正如您所推测的那样,字符串构建器可以使用“不安全”代码来提高其性能。例如,将新数据写入数组的代码已经检查过数组写入是否在边界内。通过关闭安全系统,它可以避免每次写入检查抖动可能会插入以验证每次写入阵列是否安全。字符串生成器执行许多这样的操作来执行诸如确保重用缓冲区而不是重新分配缓冲区,确保避免不必要的安全检查等等。除非你真的擅长正确编写不安全的代码,否则我建议不要使用这些恶作剧,而且确实需要剔除每一个性能。
我相信,StringBuilder
的版本在不同版本之间有所改变。从根本上说,它保持了某种形式的可变结构。我相信它曾经使用过一个仍在变异的字符串(使用内部方法),并确保它在返回后永远不会发生变异。
StringBuilder
比在循环中使用字符串连接更快的原因恰恰是因为它的可变性 - 它不需要在每次突变后构造一个新的字符串,这意味着要复制字符串中的所有数据等。
对于单个连接,使用+
实际上比使用StringBuilder
更有效。只有当你进行多项操作时,你才真正需要StringBuilder
闪耀的中间结果。
有关更多信息,请参阅my article on StringBuilder
。
Microsoft CLR确实使用内部调用执行某些操作(与不安全的代码不完全相同)。与一堆+
连接字符串相比,最大的性能优势是它写入char[]
并且不会创建尽可能多的中间字符串。当您调用ToString()时,它会根据您的内容构建一个完整的,不可变的字符串。
StringBuilder
使用可以改变的字符串缓冲区,而不是常规的String
。当你调用ToString
的StringBuilder
方法时,它只会冻结字符串缓冲区并将其转换为常规字符串,因此它不必再多次复制所有数据。
由于StringBuilder
可以更改字符串缓冲区,因此不必为字符串数据的每次更改创建新的字符串值。当您使用+
运算符时,编译器会将其转换为创建新字符串对象的String.Concat
调用。这段看似无辜的代码:
str += ",";
编译成这个:
str = String.Concat(str, ",");