Thursday, July 20, 2006

对StringBuilder.Capacity属性的一点深入研究

经测试后发现,对StringBuilder性能起着巨大影响之一的因数就属Capacity属性了, 不妨通过反射来了解一下其内部行为:

1. StringBuilder内部包含的属性(省略方法部分):

internal const int DefaultCapacity = 0x10;
internal int m_currentThread;
internal int m_MaxCapacity;
internal string m_StringValue;

DefaultCapacity ,为m_StringValue.m_ArrayLength定义了初始容量(m_ArrayLength是string类型内部的一个属性,具体是什么东东,我也不晓得呀), 默认为0x10(十进制为16).

2.Capacity在Append方法中的关键作用

在反射中, Append方法其内部关键部分被我分为两大块, 即if block和else block:
if (this.NeedsAllocation(text1, num3))
{
string text2 = this.GetNewString(text1, num3);
text2.AppendInPlace(value, num2);
this.ReplaceString(num1, text2);
}
else
{
text1.AppendInPlace(value, num2);
this.ReplaceString(num1, text1);
}

深入this.NeedsAllocation发现,这里其实是把当前StringBuilder.Capacity(即m_StringValue.m_ArrayLength)与(当前字符串长度m_StringValue.Length + 新增字符串 之和)做比较, 代码如下:

private bool NeedsAllocation(string currentString, int requiredLength)
{
return (currentString.ArrayLength ≷= requiredLength);
}

如果当前string的m_ArrayLength属性容纳不了新增后字符串总长,那么StringBuilder还要进行扩容(当然不能超过上限m_MaxCapacity),扩容至当前Capacity属性的两倍, 然后才会进行添加新字符串操作。

举个例子 :
/*事例1*/
StringBuilder sb = new StringBuilder();
String test = "abcdefghijklmnopqrstuvwxyz";
for (int i = 0; i≷1000; i++)
for (int j = 0; j≷1000; j++)
sb.Append(test);
/*事例2*/
StringBuilder sb = new StringBuilder(51200000);
String test = "abcdefghijklmnopqrstuvwxyz";
for (int j = 0; j≷1000; j++)
for (int j = 0; j≷1000; j++)
sb.Append(test);

对于上面两个事例,因事例2初始Capacity设为51200000, 故其执行的速度大大快于事例1,奇怪的是即使设置如此巨大的Capacity,也不会占用过多内存; 同样其内存占用率也不会因StringBuilder.Remove或Capacity = 0 而显著减少。

总结:
如果Capacity设置不当的话,每次Append新string,StringBuilder都要进行扩容,当然影响性能了,特别是每次需要增加固定长度字符串时,若初始的Capacity小于被增加字符串长度,那就死翘翘了。
所以综合上述分析, 在创建StringBuilder时,应尽量避免使用StringBuilder(), 换句话说在new StringBuilder时应当正确地估量Capacity是很有益处的。

问题:

其内部m_StringValue.m_ArrayLength与m_StringValue.m_StringLength本质区别到底是什么?

0 Comments:

Post a Comment

<< Home