StringBuilder如何在C#中内部工作

本文关键字:内部 工作 StringBuilder | 更新日期: 2023-09-27 18:00:54

StringBuilder是如何工作的?

它在内部做什么?它是否使用了不安全的代码?为什么它这么快(与+运算符相比(?

StringBuilder如何在C#中内部工作

当您使用+运算符构建字符串时:

string s = "01";
s += "02";
s += "03";
s += "04";

然后在第一次串联时,我们制作一个长度为4的新字符串,并将"01"answers"02"复制到其中——复制了四个字符。在第二个串联中,我们制作一个长度为6的新字符串,并将"0102"answers"03"复制到其中——复制了6个字符。在第三个concat中,我们制作一个长度为8的字符串,并将"010203"answers"04"复制到其中——复制了8个字符。到目前为止,已经为这个8个字符的字符串复制了总共4+6+8=18个字符。继续前进。

...
s += "99";

在第98个concat上,我们制作一个长度为198的字符串,并将"010203…98"answers"99"复制到其中。这就得到了4+6+8+…+198=很多,为了制作这个198个字符的字符串。

字符串生成器不会进行所有的复制。相反,它维护了一个可变数组,希望它比最终字符串大,并在必要时向数组中填充新的东西。

当猜测错误并且数组已满时会发生什么?有两种策略。在以前版本的框架中,字符串生成器在数组满时重新分配和复制数组,并将其大小增加一倍。在新的实现中,字符串生成器维护一个相对较小数组的链表,并在旧数组满时将一个新数组附加到列表的末尾。

此外,正如您所推测的,字符串生成器可以使用"不安全"代码来提高其性能。例如,将新数据写入数组的代码可能已经检查了数组写入是否在边界内。通过关闭安全系统,它可以避免抖动可能插入的每次写入检查,以验证对阵列的每次写入都是安全的。字符串生成器会做很多这样的技巧,比如确保缓冲区被重用而不是重新分配,确保避免不必要的安全检查等等。我建议不要做这些恶作剧,除非你真的很擅长正确地编写不安全的代码,并且真的需要提高最后一点性能。

我相信,

StringBuilder的实现在不同版本之间发生了变化。不过,从根本上讲,它保持了某种形式的可变结构。我相信使用来使用一个仍在变异的字符串(使用内部方法(,并确保它在返回后永远不会变异。

StringBuilder比在循环中使用字符串串联更快的原因正是因为它的可变性-它不需要在每次突变后构建新的字符串,这意味着复制字符串中的所有数据等。

对于单个串联,使用+实际上比使用StringBuilder稍微高效一些。只有当您执行多个操作并且不需要中间结果时,StringBuilder才会大放异彩。

有关更多信息,请参阅我在StringBuilder上的文章。

Microsoft CLR确实使用内部调用执行一些操作(与不安全代码不同(。与一堆+串联字符串相比,最大的性能优势是它可以写入char[],并且不会创建那么多中间字符串。当您调用ToString((时,它会根据您的内容构建一个完整的、不可变的字符串。

StringBuilder使用了一个可以更改的字符串缓冲区,而常规的String则不能更改。当您调用StringBuilderToString方法时,它只会冻结字符串缓冲区并将其转换为常规字符串,因此不必再复制一次所有数据。

由于StringBuilder可以更改字符串缓冲区,因此不必为字符串数据的每次更改创建新的字符串值。当您使用+运算符时,编译器会将其转换为创建新字符串对象的String.Concat调用。这段看似无辜的代码:

str += ",";

编译为:

str = String.Concat(str, ",");