String类是一个功能非常强大的类,它实现许多很多有用的方法。但是,String类存在一个问题,重复修改给定的字符串,效率会很低,它实际上是一个不可变的类型,这意味着一旦对字符串对象进行了初始化,该字符串对象就不能改变了。表面上修改字符串内容的方法和运算符实际上是创建了一个新字符串,根据需要,可以把旧字符串的内容复制到新字符串中。例如,考虑下面的代码:
string greetingText = "Hello from all the people at the company. ";
greetingText += "We do hope you enjoy this book as much as we enjoyed writing it.";
在执行这段代码时,首先创建了一个System.String类型的对象,并把它初始化为文本"Hello from all the people at the company. ",注意句号后面有个空格。此时.Net运行库会为该字符串分配足够的内存来保存这个文本(41个字符),在设置变量greetingText来表示这个字符串实例。
从语法上看,下一行代码是把更多的文本添加到字符串中。实际上并非如此,在此是创建了一个新字符串实例,给它分配足够的内存,以存储合并的文本(共104个字符)。把最初的文本"Hello from all the people at the company. "复制到这个新字符串中,再加上额外的文本"We do hope you enjoy this book as much as we enjoyed writing it."。然后更新存储在变量greetingText中的地址,使变量正确地指向新的字符串对象。现在没有引用旧的字符串对象——不再有变量引用它,下一次垃圾收集器清理应用程序中所有未使用的对象时,就会删除它。
这段代码本身还不错,但假定要对这个字符串编码将其中的每个字符的ASCII值加1,形成非常简单的加密模式。这就会把该字符串变成"Ifmmp gspn bmm uif qfpqmf bu uif dpnqbozXf ep ipqf zpv fokpz uijt cppl bt nvdi bt xf fokpzfe xsjujoh ju."。完成这个任务有好几种方式,但最简单、最高效的一种(假定只使用String类)是使用String.Replace()方法,该方法把字符串中指定的子字符串用另一个字符串代替。使用Replace(),对文本进行编码的代码如下:
using System;
namespace String构建字符串
{
class Program
{
static void Main(string[] args)
{
string greetingText = "Hello from all the people at the company";
greetingText += "We do hope you enjoy this book as much as we enjoyed writing it.";
Console.WriteLine($"Not encoded:\n {greetingText}");
for(int i = 'z';i>= 'a';i--){
char old1 = (char)i;
char new1= (char)(i+1);
greetingText = greetingText.Replace(old1,new1);
}
for(int i = 'Z';i>= 'A';i--){
char old1 = (char)i;
char new1 = (char)(i+1);
greetingText = greetingText.Replace(old1,new1);
}
System.Console.WriteLine($"Encoded:\n {greetingText}");
}
}
}
注:
为了简单起见,这段代码没有把Z换成A,也没有把z换成a。这些字符分别编码为[和{。
在本示例中,Replace()方法以一种智能的方式工作,在某种程度上,它并没有创建一个新字符串,除非其实际上要对旧字符串进行某些改变。原来的字符串包含23个不同的小写字母和3个不同的大写字母。所以Replace()分配一个新字符串,共计分配26次,每个新字符串都包含103个字符。因此加密过程需要在堆上有一个总共能储存2678个字符的字符串对象,该对象最终被等待的垃圾收集!显然,如果使用字符串频繁进行文字处理,应用程序就会遇到严重的性能问题。
为了解决这类问题,Microsoft提供了System.Text.StringBuilder类,StringBuilder类不像String类那样能够支持非常多的方法。在StringBuilder类上可以进行的处理仅限于替换和追加或删除字符串中的文本。
在使用String类构造一个字符串时,要给他分配足够的内存来保存字符串。然而,StringBuilder类通常分配的内存会比它需要的更多。开发人员可以选择指定StringBuilder要分配多少内存,但如果没有指定,在默认情况下就根据初始化StringBuilder实例时的字符串长度来确定所用内存的大小。StringBuilder类有两个主要的属性:
1. Length——指定包含字符串的实际长度。
2. Capacity——指定字符串在分配内存中的最大长度。
对字符串的修改就在赋予StringBuilder实例的内存块中进行,这就大大提高了追加子字符串和替换单个字符串的效率。删除或插入子字符串仍然效率低下,因为这需要移动随后的字符串部分。只有执行扩展字符串容量的操作时,才需要给字符串分配新内存,这样才能移动包含的整个字符串。在添加额外容量时,从经验来看,如果StringBuilder类检测到容量超出,且没有设置新值,就会使自己的容量翻倍。
例如,如果使用StringBuilder对象构造最初的欢迎字符串,就可以编写下面的代码:
var greetingText1 = new StringBuilder("Hello from all the people at the company. ",150);
greetingText1.Append("We do hope you enjoy this book as much as we enjoyed writing it.");
在这段代码中,为StringBuilder类设置的初始容量是150。最好把容量设置为字符串可能的最大长度,确保StringBuilder类不需要重新分配内存,因为其容量足够用了。该容量默认设置为16。理论上,可以设置尽可能大的数字,足够给该容量传送一个int值,但如果实际上给的字符串分配20亿个字符的空间,系统就可能会没有足够的内存。
然后,在调用AppendFormat()方法时,其他文本就放在空的空间中,不需要分配更多的内存。但是,多次替换文本才能获得使用StringBuilder类所带来的高效性能。例如,如果要以前面的方式加密文本,就可以执行整个加密过程,不需要分配更多的内存:
using System;
using System.Text;
namespace String构建字符串
{
class Program
{
static void Main(string[] args)
{
var greetingText = new StringBuilder("Hello from all the people at the company. ",150);
greetingText.AppendFormat("We do hope you enjoy this book as much as we enjoyed writing it.");
//greetingText.Append("We do hope you enjoy this book as much as we enjoyed writing it.");
Console.WriteLine($"Not encoded:\n {greetingText}");
for(int i = 'z';i>= 'a';i--){
char old1 = (char)i;
char new1= (char)(i+1);
greetingText = greetingText.Replace(old1,new1);
}
for(int i = 'Z';i>= 'A';i--){
char old1 = (char)i;
char new1 = (char)(i+1);
greetingText = greetingText.Replace(old1,new1);
}
System.Console.WriteLine($"Encoded:\n {greetingText}");
}
}
}
这段代码使用了StringBuilder.Replace()方法,它的功能于String.Replace()一样,但不需要在过程中复制字符串。在上述代码中,为存储字符串而分配的总存储单元是用于StringBuilder实例的150个字符,以及在最后一条Console.WriteLine()语句中执行字符串操作期间分配的内存。
一般而言,使用StringBuilder类执行字符串的任何操作,而使用Stirng类存储字符串或显示最终结果。
本文探讨了C#中String类的不可变性及其在频繁修改时的效率问题,提出StringBuilder类作为更高效的替代方案。String类在修改时会创建新对象,导致内存消耗。而StringBuilder在内存块内进行修改,减少了内存分配,提高了性能。通过示例代码展示了StringBuilder在追加、替换等操作中的优势,并提供了合理设置容量以避免内存再分配的建议。

被折叠的 条评论
为什么被折叠?



