我已经创建了测试应用程序进行测试,StringBuilder 是否将数据复制到另一个实例并在其长度超过当前容量时增加它的缓冲区,并在 ildasm.exe 中进行验证,但它看起来相同。
如何验证 StringBuilder 会将其数据复制到新实例中并按指定限制增长缓冲区?
我已经创建了测试应用程序进行测试,StringBuilder 是否将数据复制到另一个实例并在其长度超过当前容量时增加它的缓冲区,并在 ildasm.exe 中进行验证,但它看起来相同。
如何验证 StringBuilder 会将其数据复制到新实例中并按指定限制增长缓冲区?
容量表示分配给 StringBuilder 的连续内存。容量可以 >= 字符串的长度。当附加到 StringBuilder 的数据多于容量时,StringBuilder 会自动增加容量。由于已经超过容量(即连续内存被填满,没有更多可用的缓冲空间),因此分配了更大的缓冲区并将数据从原始内存复制到这个新区域。
它不会将数据复制到新的“实例”,而是复制到新的“内存位置”。该实例保持不变,但指向新的内存位置。
编辑仅供参考:如果在创建期间未指定,则 StringBuilder 的默认容量为 16
如果您想查看 StringBuilder 的内存位置,那么您可以调试您的应用程序并使用 Debug > Windows > Memory 检查内存。当 Append stmt 运行时,您实际上可以看到存储在 StringBuilder 中的每个字节的地址。
如果您需要以编程方式获取位置,此链接可能会有所帮助。
并不是说我们真的在测试 StringBuilder 是否有效,因为它确实有效,但是为了您自己的乐趣,您总是可以编写一个单元测试。
StringBuilder sb = new StringBuilder(10);
Console.WriteLine("Capacity = " + sb.Capacity + " Length = " + sb.Length
+ " MaxCapacity = " + sb.MaxCapacity);
sb.Append("1234567890");
sb.Append("1234567890");
sb.Append("1234567890");
Console.WriteLine("Capacity = " + sb.Capacity + " Length = " + sb.Length
+ " MaxCapacity = " + sb.MaxCapacity);
Assert.AreEqual("123456789012345678901234567890"
, sb.ToString()); // NUnit assert.
不出所料,它通过了,并给出了以下输出。
容量 = 10 长度 = 0 最大容量 = 2147483647 容量 = 40 长度 = 30 最大容量 = 2147483647
如果您想检查 StringBuilder 是如何实现的,只需启动 Reflector 并查看它。的实现StringBuilder.Append(string)
如下
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;
}
查看带有 等的部分NeedsAllocation
以GetNewString
找到您要查找的内容。
StringBuilder 在需要时增加其缓冲区的方式由 StringBuilder 的内部代码处理;它不会显示在您的应用程序的 IL 代码中;编译器无法知道某个方法中的 StringBuilder 将包含多大的字符串,因为这可能会不时发生变化。
但是,当 StringBuilder确实增加其缓冲区时,它不会产生新的 StringBuilder 实例。这可能会导致它将其保存的字符串的内部表示复制到一个新实例中(我对类的内部工作了解得不够多,无法准确说明会发生什么)。
class Program
{
static void Main()
{
StringBuilder sb = new StringBuilder();
Console.WriteLine(sb.Capacity); //16
for (int i = 0; i < 50; i++)
sb.Append(i + ",");
Console.WriteLine(sb.Capacity); //256
sb = new StringBuilder();
Console.WriteLine(sb.Capacity); //16
}
}
您可以使用 Reflector 来了解 StringBuilder 的工作原理。
见方法
StringBuilder Append(string value)
对于 .Net 3.5 逻辑是这样的:如果缓冲区长度不足以容纳新字符串,则创建长度等于 Max(oldSize * 2, requiredSize) 的新缓冲区。
换句话说,StringBuffer 尝试将缓冲区加倍,如果这还不够,则使缓冲区大小刚好足以容纳新字符串。
对旧缓冲区的引用被删除,旧缓冲区在下一次垃圾回收中被回收。