C#中的字符串——用Stringbuilder类很重要

注:这篇文章基本是《C#高级编程》(第七版)第九章的学习笔记。

众所周知,C#中处理字符串通常用的都是string,它其实是.NET基础类System.String类的映射。注意一个是小写一个是大写。我觉得这种设计可能是为了在使用基础功能时让代码看起来就像在使用C风格的字符串。

String类功能强大,但是有一个问题:重复修改一个string实例,效率会很低。原因是,string对象在初始化之后就不可变了,修改string对象的方法实际上是创建新的实例。例如:

string greetingText = "First message, ";
greetingText += "Second message";

这段代码实际上做的事情是:首先,创建一个string对象,并将它初始化为文本"First message, "。然后在把对象的引用赋值给变量greetingText。第二句则创建了一个新的string对象,然后将其初始化为两个字符串连接的结果,最后将变量greetingText赋值为这个新对象的引用。这样一来,greetingText原来所引用的对象就没有被任何变量引用了,它将被垃圾收集机制销毁掉。

这种情况看起来还好,仅仅是多创建了一个对象而已。但考虑下面的情况:

把字符串中的每个字符都用其在字母表中的后一个字符来代替。

使用string的Replace方法最简单:

for (int i = 'z'; i >= 'a'; i--)
{
    char old1 = (char)i;
    char new1 = (char)(i+1);
    greetingText = greetingText.Replace(old1, new1);
}

简便起见,只写了转换小写字符的代码,也没有考虑字符'z'的情况。因此'z'会被替换为'{'。

问题来了:假如greetingText含有从'a'到'z'的所有字符,那么这段代码就需要创建26个新string对象。如果字符串较大而且调用频繁,就会出现性能问题。

System.Text.StringBuilder类就是为了解决这类问题。它能够高效工作,但是支持的方法较少。它包含的操作仅限于替换、追加或删除字符串。

可以推测,StringBuilder类对字符串的修改需要在分配给其实例的内存中操作。这样,追加和替换字符就很高效。但是删除和插入依然效率不高。

StringBuilder类有两个重要属性:

  • Length指定字符串的实际长度;
  • Capacity指定分配给字符串的内存的最大长度。

创建StringBuilder类时,可以指定Capacity(容量)的值(一般大于字符串的长度,默认值是16)。在处理时如果容量不够, StringBuilder会将容量翻倍。这个特点和C++中的容器很像。

同样写上面的程序:

StringBuilder greetingBuilder = new StringBuilder("First message, ", 30);
greetingBuilder.Append("Second message");

for (int i = 'z'; i >= 'a'; i--)
{
    char old1 = (char)i;
    char new1 = (char)(i+1);
    greetingBuilder = greetingBuilder.Replace(old1, new1);
}

注意StringBuilder的创建方法和追加字符串的方法都和string不同。注意构造函数的第二个参数容量设为了30。

这段代码只创建了一个StringBuilder对象。并且所有的操作都是在这个对象的内存块中进行的,所以效率很高。

——昨天碰到了一个极端的例子:把string类用StringBuilder类替换后,速度快了60多倍!这个程序是处理一个很长的字符串,对这个字符串的每个字符做循环,循环内执行string的追加操作。这就是StringBuilder类的典型应用场景。从前文可以知道,用string会非常慢。这个时候就一定要用StringBuilder类。

但注意:StringBuilder类不能总是提高性能。其应在处理多个字符串时使用。如果只是连接两个字符串等简单操作,使用String类较好。

另:不能把StringBuilder强制转换为String,只能用ToString()方法。