Spiga

串(五):.NET Framework StringBuilder类

2012-04-29 13:45:29

一、概述

  可变长度的字符串操作类。
  以System.String为其数据保存和操作基础。
  在字符串操作过程中减少了对象创建次数,但很多情况下仍然会有额外对象创建出来。

二、类的声明

  Stringbuilder中有两个非常重要的字段m_currentThread和m_StringValue。前者保存的是Stringbuilder对象拥有者的线程id,用于判断多线程情况下操作Stringbuilder对象是否需要创造副本;后者是以个String类型的对象,用于保存Stringbuilder的实际数据对象,这就说明Stringbuilder是以System.String对象来保存和操作的。

public sealed class StringBuilder : ISerializable {
	internal IntPtr m_currentThread = Thread.InternalGetCurrentThread();    //对象拥有者的线程id
	internal volatile String m_StringValue = null;      //保存stringbuilder的实际数据的对象
	 //other code
}

三、构造函数

  提供7种构造的重载形式用来构建对象,其中有一种是私有重载用于对象的序列化。其他6种重载中主要的代码结构如下

public StringBuilder(String value, int startIndex, int length, int capacity) {
	if (capacity<0) {
		throw new ArgumentOutOfRangeException("capacity", 
											  String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("ArgumentOutOfRange_MustBePositive"), "capacity"));
	}
	if (length<0) {
		throw new ArgumentOutOfRangeException("length", 
											  String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("ArgumentOutOfRange_MustBeNonNegNum"), "length"));
	}

	if (startIndex<0) {
		throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));
	}

	if (value == null) {
		value = String.Empty;
	}

	if (startIndex > value.Length - length) {
		throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_IndexLength"));
	}

	m_MaxCapacity = Int32.MaxValue;

	if (capacity == 0) {
		capacity = DefaultCapacity;
	}
	//如果预计容量小于初始化字符串长度,那么将长度设置成当前的2倍
	//如果在扩容后发生了溢出,那么容量直接设置为初始化字符串的长度
	while (capacity < length) {
		capacity *= 2;
		// If we overflow, we should just use length as capacity. 
		// There is no reason we should throw an exception in this case if the system is able 
		// to allocate the string.
		if(capacity < 0 ) {
			capacity = length;
			break;
		}
	}
	//获取字符串对象的副本
	m_StringValue = String.GetStringForStringBuilder(value, startIndex, length, capacity);
}

从上面的代码可以看出,字符串对象的创建将调用String类中一个名为GetStringForStringBuilder的方法,我们再来看这个方法的构成

//为stringbuilder类构造字符串,创建value字符串的副本
unsafe internal static String GetStringForStringBuilder(String value, int startIndex, int length, int capacity) {
	BCLDebug.Assert(value!=null, "[String.GetStringForStringBuilder]value!=null");
	BCLDebug.Assert(capacity>=length,  "[String.GetStringForStringBuilder]capacity>=length");

	String newStr = FastAllocateString(capacity);
	if (value.Length==0) {
		newStr.SetLength(0);
		return newStr;
	}
	fixed (char* dest = &newStr.m_firstChar)
		fixed (char* src = &value.m_firstChar) {
			wstrcpy(dest, src + startIndex, length);
		}
	newStr.SetLength(length);
	return newStr;
}

这里可以证实前面讲的“System.String为StringBuilder类提供基础方法”,实际上System.String类还提供了很多基础方法供StringBuilder类使用。

四、Append方法的实现

  Append方法的重载很多,我选其中一个来说明它的实现,其他的重载实现大同小异。方法的实现主要有3个步骤:首先计算保存结果字符串所需要的内存空间,看看是否需要扩容。如果不需要则跳过第二步,如果需要则扩容并拿到原字符串填充后的新的字符串对象。最后调用String类提供的AppendInPlace基础方法来实现Stringbuilder.Append方法。我们来看代码

//重置repeatCount次在原字符串后追加value字符
public StringBuilder Append(char value, int repeatCount)
{
	if (repeatCount == 0)
	{
		return this;
	}
	if (repeatCount < 0)
	{
		throw new ArgumentOutOfRangeException("repeatCount", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount"));
	}

	IntPtr tid;
	String currentString = GetThreadSafeString(out tid);
	//计算保存结果字符串所需要的内存空间
	int currentLength = currentString.Length;
	int requiredLength = currentLength + repeatCount;

	if (requiredLength < 0)
		throw new OutOfMemoryException();
	//判断当前字符串缓冲区是否需要扩容
	if (!NeedsAllocation(currentString, requiredLength))
	{
		//扩容并拿到原字符串填充后的新的字符串对象
		currentString.AppendInPlace(value, repeatCount, currentLength);
		ReplaceString(tid, currentString);
		return this;
	}

	String newString = GetNewString(currentString, requiredLength);
	//通过调用stirng类的基础方法来实现Append操作
	newString.AppendInPlace(value, repeatCount, currentLength);
	ReplaceString(tid, newString);
	return this;
}

//更新字符串值及其所有者
//注意该函数被调用后字符串的所有者会发生更改,此更改可能会带来后面其他线程对于字符串操作时所带来的制作副本的开销
private void ReplaceString(IntPtr tid, String value)
{
	BCLDebug.Assert(value != null, "[StringBuilder.ReplaceString]value!=null");

	m_currentThread = tid;
	m_StringValue = value;
}

其中String.AppendInPlace方法实现如下

//将value追加在原字符串的currentLength位置后
//该函数被stringbuilder.Append函数所使用
internal unsafe void AppendInPlace(String value, int currentLength)
{
	int count = value.Length;
	int newLength = currentLength + count;

	BCLDebug.Assert(value != null, "[String.AppendInPlace]value!=null");
	BCLDebug.Assert(newLength < this.m_arrayLength, "[String.AppendInPlace]Length is wrong.");

	fixed (char* dest = &this.m_firstChar)
	{
		fixed (char* src = &value.m_firstChar)
		{
			wstrcpy(dest + currentLength, src, count);
		}
		dest[newLength] = '\0';
	}
	this.m_stringLength = newLength;
}
复制代码

Stringbuilder类的其他方法这里就不介绍了,实现与Append方法步骤差不多都一致。