- C#高级编程(第10版) C# 6 & .NET Core 1.0 (.NET开发经典名著)
- (美)Christian Nagel
- 2799字
- 2025-02-18 01:49:52
10.1 System.String类
在介绍其他字符串类之前,先快速复习一下String类中一些可用的方法。
System.String类专门用于存储字符串,允许对字符串进行许多操作。此外,由于这种数据类型非常重要,C#提供了它自己的关键字和相关的语法,以便使用这个类来轻松地处理字符串。
使用运算符重载可以连接字符串:
string message1 = "Hello"; // returns "Hello" message1 += ", There"; // returns "Hello, There" string message2 = message1 + "! "; // returns "Hello, There! "
C#还允许使用类似于索引器的语法来提取指定的字符:
string message = "Hello"; char char4 = message[4]; // returns 'o'. Note the string is zero-indexed
这个类可以完成许多常见的任务,如替换字符、删除空白和把字母变成大写形式等。可用的方法如表10-1所示。
表10-1

注意:表10-1并不完整,但可以让你明白字符串所提供的功能。
10.1.1 构建字符串
如上所述,String类是一个功能非常强大的类,它实现许多很有用的方法。但是,String类存在一个问题:重复修改给定的字符串,效率会很低,它实际上是一个不可变的数据类型,这意味着一旦对字符串对象进行了初始化,该字符串对象就不能改变了。表面上修改字符串内容的方法和运算符实际上是创建一个新字符串,根据需要,可以把旧字符串的内容复制到新字符串中。例如,考虑下面的代码(代码文件StringSample/Program.cs):
string greetingText = "Hello from all the guys at Wrox Press. "; greetingText += "We do hope you enjoy this book as much as we enjoyed writing it.";
本章的示例使用如下依赖项和名称空间(除非特别说明):
依赖项:
NETStandard.Library
名称空间:
System System.Text static System.Console
在执行这段代码时,首先创建一个System.String类型的对象,并把它初始化为文本“Hello from all the guys at Wrox Press. ”,注意句号后面有一个空格。此时.NET运行库会为该字符串分配足够的内存来保存这个文本(39个字符),再设置变量greetingText来表示这个字符串实例。
从语法上看,下一行代码是把更多的文本添加到字符串中。实际上并非如此,在此是创建一个新字符串实例,给它分配足够的内存,以存储合并的文本(共103个字符)。把最初的文本“Hello from all the people at Wrox Press. ”复制到这个新字符串中,再加上额外的文本“We do hope you enjoy this book as much as we enjoyed writing it.”。然后更新存储在变量greetingText中的地址,使变量正确地指向新的字符串对象。现在没有引用旧的字符串对象——不再有变量引用它,下一次垃圾收集器清理应用程序中所有未使用的对象时,就会删除它。
这段代码本身还不错,但假定要对这个字符串编码,将其中的每个字符的ASCII值加1,形成非常简单的加密模式。这就会把该字符串变成“Ifmmp gspn bmm uif hvst bu Xspy Qsftt. Xf ep ipqf zpv fokpz uijt cppl bt nvdi bt xf fokpzfe xsjujoh ju”。完成这个任务有好几种方式,但最简单、最高效的一种(假定只使用String类)是使用String.Replace()方法,该方法把字符串中指定的子字符串用另一个子字符串代替。使用Replace(),对文本进行编码的代码如下所示:
string greetingText = "Hello from all the guys at Wrox Press. "; greetingText += "We do hope you enjoy this book as much as we " + "enjoyed writing it."; 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); } 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类有两个主要的属性:
● Length指定包含字符串的实际长度。
● Capacity指定字符串在分配的内存中的最大长度。
对字符串的修改就在赋予StringBuilder实例的内存块中进行,这就大大提高了追加子字符串和替换单个字符的效率。删除或插入子字符串仍然效率低下,因为这需要移动随后的字符串部分。只有执行扩展字符串容量的操作时,才需要给字符串分配新内存,这样才能移动包含的整个字符串。在添加额外的容量时,从经验来看,如果StringBuilder类检测到容量超出,且没有设置新值,就会使自己的容量翻倍。
例如,如果使用StringBuilder对象构造最初的欢迎字符串,就可以编写下面的代码:
var greetingBuilder = new StringBuilder("Hello from all the guys at Wrox Press. ", 150); greetingBuilder.AppendFormat("We do hope you enjoy this book as much " + "as we enjoyed writing it");
注意:为了使用StringBuilder类,需要在代码中引用System.Text类。
在这段代码中,为StringBuilder类设置的初始容量是150。最好把容量设置为字符串可能的最大长度,确保StringBuilder类不需要重新分配内存,因为其容量足够用了。该容量默认设置为16。理论上,可以设置尽可能大的数字,足够给该容量传送一个int值,但如果实际上给字符串分配20亿个字符的空间(这是StringBuilder实例理论上允许拥有的最大空间),系统就可能会没有足够的内存。
然后,在调用AppendFormat()方法时,其他文本就放在空的空间中,不需要分配更多的内存。但是,多次替换文本才能获得使用StringBuilder类所带来的高效性能。例如,如果要以前面的方式加密文本,就可以执行整个加密过程,无须分配更多的内存:
var greetingBuilder = new StringBuilder("Hello from all the guys at Wrox Press. ", 150); greetingBuilder.AppendFormat("We do hope you enjoy this book as much " + "as we enjoyed writing it"); WriteLine("Not Encoded:\n" + greetingBuilder); for(int i = 'z'; i>='a'; i--) { char old1 = (char)i; char new1 = (char)(i+1); greetingBuilder = greetingBuilder.Replace(old1, new1); } for(int i = 'Z'; i>='A'; i--) { char old1 = (char)i; char new1 = (char)(i+1); greetingBuilder = greetingBuilder.Replace(old1, new1); } WriteLine("Encoded:\n" + greetingBuilder);
这段代码使用了StringBuilder.Replace()方法,它的功能与String.Replace()一样,但不需要在过程中复制字符串。在上述代码中,为存储字符串而分配的总存储单元是用于StringBuilder实例的150个字符,以及在最后一条Console.WriteLine()语句中执行字符串操作期间分配的内存。
一般而言,使用StringBuilder类执行字符串的任何操作,而使用String类存储字符串或显示最终结果。
10.1.2 StringBuilder成员
前面介绍了StringBuilder类的一个构造函数,它的参数是一个初始字符串及该字符串的容量。StringBuilder类还有几个其他的构造函数。例如,可以只提供一个字符串:
var sb = new StringBuilder("Hello");
或者用给定的容量创建一个空的StringBuilder类:
var sb = new StringBuilder(20);
除了前面介绍的Length和Capacity属性外,还有一个只读属性MaxCapacity,它表示对给定的StringBuilder实例的容量限制。在默认情况下,这由int.MaxValue给定(大约20亿,如前所述)。但在构造StringBuilder对象时,也可以把这个值设置为较低的值:
// This will set the initial capacity to 100, but the max will be 500. // Hence, this StringBuilder can never grow to more than 500 characters, // otherwise it will raise an exception if you try to do that. var sb = new StringBuilder(100, 500);
还可以随时显式地设置容量,但如果把这个值设置为小于字符串的当前长度,或者是超出了最大容量的某个值,就会抛出一个异常:
var sb = new StringBuilder("Hello"); sb.Capacity = 100;
StringBuilder类主要的方法如表10-2所示。
表10-2

其中一些方法还有几种重载版本。
注意:AppendFormat()方法实际上会在最终调用Console.WriteLine()方法时被调用,它负责确定所有像{0:D}的格式化表达式应使用什么表达式替代。下一节讨论这个问题。
不能把StringBuilder强制转换为String(隐式转换和显式转换都不行)。如果要把StringBuilder的内容输出为String,唯一的方式就是使用ToString()方法。
前面介绍了StringBuilder类,说明了使用它提高性能的一些方式。但要注意,这个类并不总能提高性能。StringBuilder类基本上应在处理多个字符串时使用。但如果只是连接两个字符串,使用System.String类会比较好。