【发布时间】:2010-10-27 05:30:57
【问题描述】:
我已经创建了测试应用程序来测试,StringBuilder 是否将数据复制到另一个实例并在其长度超过当前容量时增加它的缓冲区,并在 ildasm.exe 中进行验证,但看起来相同。
如何验证 StringBuilder 会将其数据复制到新实例中并按指定限制增长缓冲区?
【问题讨论】:
标签: c# stringbuilder
我已经创建了测试应用程序来测试,StringBuilder 是否将数据复制到另一个实例并在其长度超过当前容量时增加它的缓冲区,并在 ildasm.exe 中进行验证,但看起来相同。
如何验证 StringBuilder 会将其数据复制到新实例中并按指定限制增长缓冲区?
【问题讨论】:
标签: c# stringbuilder
Capacity 表示分配给 StringBuilder 的连续内存。容量可以 >= 字符串的长度。当附加到 StringBuilder 的数据多于容量时,StringBuilder 会自动增加容量。由于已经超过容量(即连续内存被填满,没有更多的缓冲空间可用),因此分配了更大的缓冲区并将数据从原始内存复制到这个新区域。
它不会将数据复制到新的“实例”,而是复制到新的“内存位置”。实例保持不变,但指向新的内存位置。
编辑 仅供参考:如果在创建期间未指定,则 StringBuilder 的默认容量为 16
如果您想查看 StringBuilder 的内存位置,那么您可以使用调试 > Windows > 内存来调试您的应用程序并检查内存。当 Append stmt 运行时,您实际上可以看到存储在 StringBuilder 中的每个字节的地址。
如果您需要以编程方式获取位置,this link 可能会有所帮助。
【讨论】:
maxCapacity 参数。由于潜在的碎片化,它是否也与概念上的最大长度不同?当我超过容量时会发生什么?如果它按我期望的方式工作,那么我可以使用这个类来构建一个命令行字符串,由某个对参数长度有限制的可执行文件执行。
如果您想检查 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 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 在需要时增加其缓冲区的方式由 StringBuilder 的内部代码处理;它不会显示在您的应用程序的 IL 代码中;编译器无法知道某个方法中的 StringBuilder 将包含多大的字符串,因为这可能会不时发生变化。
然而,当 StringBuilder 确实增加它的缓冲区时,它不会产生一个新的 StringBuilder 实例。这可能会导致它将其持有的字符串的内部表示复制到一个新实例中(我对类的内部工作了解不够多,无法准确说明会发生什么)。
【讨论】:
您可以使用 Reflector 来了解 StringBuilder 的工作原理。
查看方法
StringBuilder Append(string value)
.Net 3.5 的逻辑是这样的:如果缓冲区长度不足以容纳新字符串,则创建长度等于 Max(oldSize * 2, requiredSize) 的新缓冲区。
换句话说,StringBuffer 尝试将缓冲区加倍,如果这还不够,则使缓冲区大小刚好足以容纳新字符串。
对旧缓冲区的引用被删除,旧缓冲区在下一次垃圾回收时被回收。
【讨论】:
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
}
}
【讨论】: