20.4 使用StringBuilder类

鉴于String在某些情况下的效率低下问题,例如在循环中改变字符串的值,将产生非常多的中间对象,这些对象将造成存储浪费和效率降低。为了应对这种问题,BCL提供了StringBuilder类用以高效地处理字符串,该类位于System.Text命名空间。StringBuilder类是一种可变字符串,使用StringBuilder类可以在不创建新的对象的情况下改变字符串的值,包括追加、删除、修改等操作。最常用的初始化StringBuilder类的新实例的方式有三种:

❑StringBuilder sb1=new StringBuilder();

❑StringBuilder sb2=new StringBuilder(64);

❑StringBuilder sb3=new StringBuilder("Hello world!");

第一种方式实际上使用了默认容量初始化StringBuilder类的新实例,这个默认的容量为32。容量定义了可存储在当前实例所分配的内存中的最大字符数,如果当前实例中存储的字符数超过了容量值,则StringBuilder对象将分配更多内存来存储它们。默认的字符串值为String.Empty。

将StringBuilder和String类对比,String值的追加操作每次都会分配新的内存,而StringBuilder仅当StringBuilder的对象缓冲区太小以至于无法容纳新数据时才分配内存。因此,当字符串追加操作并不多时,最好还是使用String类。某些情况下,编译器甚至会将多个字符串的联结操作组合到一个操作中。但如果需要任意多次追加字符串,例如在一个循环中,使用StringBuilder类比使用String类性能更高。尽管StringBuilder有着诸多好处,但创建一个StringBuilder实例比创建一个String实例需要花费更多的内存资源,因此使用StringBuilder也是要讲场合的,这一点需要读者注意。

接下来,通过一段示例代码来演示StringBuilder的用法,在示例代码中分别使用StringBuilder类和String类创建一个10 000行的字符串,其中每行包含一个日期字符串,最后将日期中的斜杠"/"替换成"-"符号。我们会分别统计使用两个类各自所需要的时间,以此来比较在多次改变一个字符串的值时这两个类的效率差别。具体请参看代码清单20-10。

代码清单20-10 StringBuilder和String类的效率对比


using System;

using System.Text;

namespace ProgrammingCSharp4

{

class StringSample

{

public static void Main()

{

const int numberOfLoops=10000;

StringBuilder myBuilder=new StringBuilder();

DateTime endDate=DateTime.Now.AddDays(numberOfLoops);

DateTime theDate=DateTime.Now;

DateTime startTime=DateTime.Now;

const int aproxLengthOfNewString=11*numberOfLoops;

myBuilder.Capacity=aproxLengthOfNewString;

while(theDate<endDate)

{

myBuilder.AppendLine(theDate.ToShortDateString());

theDate=theDate.AddDays(1);

}

myBuilder.Replace("/","-");

string testString=myBuilder.ToString();

TimeSpan interval=TimeSpan.FromTicks(DateTime.Now.Ticks-startTime.Ticks);

Console.WriteLine(

“StringBuilder:花费{0}毫秒追加{1}个字符串,字符串长度为{2}.”,

interval.TotalMilliseconds,numberOfLoops,testString.Length);

Console.WriteLine();

testString=“";

theDate=DateTime.Now;

startTime=DateTime.Now;

while(theDate<endDate)

{

testString+=theDate.ToShortDateString()+"\n";

theDate=theDate.AddDays(1);

}

testString.Replace("/","-");

interval=TimeSpan.FromTicks(DateTime.Now.Ticks-startTime.Ticks);

Console.WriteLine(

“String:花费{0}毫秒追加{1}个字符串,字符串长度为{2}”,

interval.TotalMilliseconds,numberOfLoops,testString.Length);

Console.WriteLine();

}

}

}


为了让结果更准确,我们运行了3次,如下是这3次的运行结果:

第一次运行:


StringBuilder:花费46.875毫秒追加10000个字符串,字符串长度为109588.

String:花费4656.25毫秒追加10000个字符串,字符串长度为99588


第二次运行:


StringBuilder:花费46.875毫秒追加10000个字符串,字符串长度为109588.

String:花费4765.625毫秒追加10000个字符串,字符串长度为99588


第三次运行:


StringBuilder:花费62.5毫秒追加10000个字符串,字符串长度为109588.

String:花费4671.875毫秒追加10000个字符串,字符串长度为99588


可见,此时StringBuilder的工作效率达到了String的近100倍。因此基于性能考虑,在循环中操作字符串的追加操作时,应该使用StringBuilder代替String。