友情链接: 互动百科 CSDN.NET 百度音乐 和讯理财 世界杯吧 拉手网

.NET 框架中的字符串

.NET 框架中的字符串
目录

上期主题和本期主题
字符串
System.String 类
C# 中的字符串
Visual Basic .NET 中的字符串
使用标准 .NET 框架 API 的字符串
StringBuilder
System.Text 编码器和解码器
正则表达式
试一试!
本期主题和下期主题

欢迎使用这里的源代码!请畅所欲言!

并请确保查看作为 Microsoft® ASP.NET 应用程序运行的示例(连同源代码),或者直接在新窗口中查看源文件。(请注意,在示例文件中,程序员的注释使用的是英文,本文中将其译为中文是为了便于读者进行理解。)

上期主题和本期主题

上一次,我们讨论了所有 .NET 类的“父母”:古老的 System.Object。我们还讨论了内存分配和回收,并简单讨论了动态类型系统在 .NET 框架中的工作方式。

这次,我们将讨论 .NET 运行时中的字符串。

字符串

在 .NET 框架中,String(更确切地说是 System.String)是字符串类型(包括字符串常值)。在 .NET 框架中,一个字符串可以不包括或包括多个 16 位(两个字节)的 Unicode 字符。

字符串不可改变

在 .NET 框架中有几点让人觉得不习惯,其中之一就是 String 对象是不可改变的,也就是说一旦创建了这些对象,就不能再改变它们的值。(但是,您可以重新指定字符串引用以指向另一个字符串,如果没有其他指向第一个字符串的引用,则可以释放它以回收内存。)

用于操作字符串的 String 对象的方法并不更改当前字符串,而是创建并返回新的字符串。即使只是更改、插入或删除一个字符,也会导致创建新的字符串并废弃原字符串。

请注意,反复创建和废弃字符串的过程会非常缓慢。但不可改变的字符串也有许多好处,使用不可改变的对象,处理所有权、别名和线程等问题都简单许多。例如,由于不能修改字符串,因此一个线程不会通过修改字符串来影响另一个线程,所以在多线程编程中字符串始终是安全的。

StringBuilder 救援

了解 StringBuilderStringBuilder 对象不是字符串,但用于操作由 16 位 Unicode 字符组成的字符串。它包含一个通常使用字符串(通常大于要处理的字符串)初始化的缓冲区。缓冲区中的字符可以直接操作,而不必创建新的缓冲区,您可以插入、附加、删除和替换字符。(当然,如果插入的字符太多,使原缓冲区溢出,还是会创建新的缓冲区。)完成对字符的操作后,使用 StringBuilderToString 方法从中提取已完成的字符串。多数情况下,StringBuilder.ToString 不涉及复制操作,所以效率很高。

为 String 和 StringBuilder 建立索引

由于这两种对象都包含 Char 类型的 Unicode 字符,所以它们都支持名为 Chars 的索引生成器。当您指定一个索引时,此索引生成器返回一个 Char 字符。如果使用 C#,则可以象使用数组下标那样访问此索引生成器。

由于 String 不可改变,因而其索引生成器是只读的。当然,StringBuilder 索引生成器是可读/写的,所以您可以查看和更改字符。

System.String 类

System.String 是一个密封类,这意味着您不能从其继承,同时也意味着系统可以进行适当的优化以加快字符串的处理。

大多数 .NET 语言都包含一些内置的字符串支持。例如,这些语言通常支持字符串常值和诸如并置等操作。但各种语言使用的语法不同。

C# 中的字符串

C# 中的字符串常值

C# 支持两种字符串常值:“正则”字符串常值和“逐字”字符串常值。

正则字符串常值与 C 和 C++ 中的字符串常值类似:它们使用引号分隔,可以包含转义序列以代表各种控制字符和任何 Unicode 字符。它们的长度不能超过一行,但在编译时可以使用 + 运算符并置相邻的字符串常值。所有转义序列均以反斜杠 ("\") 开头,如下表所列。

表 1:C# 字符串常值转义序列

转义序列 说明
\t 制表符 (Unicode 0x0009)。
\r 回车符 (0x000d)。
\n 新行(换行符)(0x000a)。
\v 垂直制表符 (0x000b)。
\a 警报 (0x0007)。
\b 退格 (0x0008)。
\f 换页符 (0x000c)。
\0 Null (0x0000)。
\\ 反斜杠 (0x005c)。
\' 单引号 (0x0027)。
\" 双引号 (0x0022)。
\xD 可变位数的十六进制字符代码。
\uABCD Unicode 字符 0xABCD(u 或 U 都可以,其中 A、B、C 和 D 是有效的十六进制位 0-9、a-f 和 A-F)。

任何正则字符串常值或字符常值中都可以包含这些转义序列(如 '\t')。此外,标识符中也可以包含 Unicode 转义序列,所以 "if (a\U0066b == true)" 与 "if (afb == true)" 是相同的(因为 Unicode 0x0066 即为 "f")。所以,您可以使用任何有效的 Unicode 字符来编写标识符,即使该字符不在您的键盘上或者无法以编辑器使用的字体显示。

逐字字符串常值以 @" 开头,以匹配的引号结束。它们不包含转义序列。所以

@"\\machine\share\path1\path2\file.ext"

等同于

"\\\\machine\\share\\path1\\path2\\file.ext"

但前者要简单得多,且不易发生错误。逐字字符串常值的长度还可以超过一行。此时,它们将包含引号之间的所有空格字符:

@"First \t line
tabbed second line"
// 等同于 "First \\t line\r\n\ttabbed second line"
注意:第二个字符串中是否需要“\r”取决于编辑器。 Microsoft® Visual Studio® .NET 在每行末尾使用一个回车符/换行符对,所以,如果您使用 Visual Studio .NET 编辑器创建文件,则需要这两种符号。而其他编辑器可能只使用换行符来结束行(ASCII 码 10 或换行符)。

逐字字符串常值的“不包含转义序列”规则的唯一例外情况就是,在逐字字符串中用双重双引号表示一个双引号:

@"This is a ""quote"" from me."
// 等同于 "This is a \"quote\" from me."

C# 中的字符串操作

C# 语言支持以下字符串操作:

  • 建立字符串索引以读取(但不能写入)单个字符,如 s[i]
  • 使用 + 运算符并置两个字符串,如 s + tst 可以不是字符串,如果这两者之一不是字符串,则会对它调用 ToString 将其转换成字符串。+ 运算符是 C# 语言的一部分,而不是 String 类的成员。如果操作数之一为 null(与空字符串 "" 不同),它将被转换成空字符串。如果可以在编译时执行该操作,则在编译时执行。
  • 等同和不等同,如 s == ts != t。这些运算符是 String 类的一部分,所以在支持重载运算符的任何语言中都可以使用它们。通过使用成员名 op_Equalityop_Inequality 而非运算符语法,即使不支持重载运算符的语言(如 Microsoft® Visual Basic® .NET)也可以使用它们。这些运算符将调用 String.Equals,它对两个字符串中的字符进行二进制比较,而不是进行区分环境的比较。

如果可能,并置将在编译时完成。您也可以将任何对象与字符串并置,从而并置该对象的 ToString 方法返回的值。例如,下面所有字符串都是有效的:

string s0 = " Hello ";
string s1 = s0 + 5; // " Hello 5"
string s2 = 6.3 + s1; // "6.3 Hello 5"
string ret = String.Format("s0: {0}\ns1: {1}\ns2: {2}", s0, s1, s2);

此外,由于字符串不可改变,编译器和运行时将合并重复的字符串常值,这样程序中只包含每个字符串常值的一个副本。

Visual Basic .NET 中的字符串

Visual Basic .NET 中的字符串常值

Visual Basic .NET 字符串常值非常简单:由包含在双引号中的一组 Unicode 字符组成。如果要在字符串中包含双引号,则需要使用双重双引号,如:

"This prints ""Hello, world!"" on the screen"

Visual Basic .NET 字符串常值或字符常值不存在转义序列机制,但可以包含双引号。(这与 C# 逐字字符串常值类似,只是 Visual Basic .NET 字符串常值不能包含行尾字符,而只能包含在一行上。)如果需要包括在 Visual Basic .NET 程序中无法表示的字符(如换行符),则需要使用 ChrChrW 函数通过代码创建该字符,然后将其与字符串并置。请注意,使用回车符 (Chr(13)) 会导致两行在屏幕上彼此覆盖,因为回车后并没有换行。如果需要同时使用回车符和换行符,可以使用 Environment.NewLine 属性:

"First line" & Chr(10) & "Second line" ' 仅回车
"First line" & Environment.NewLine & "Second line" ' 回车并换行

Visual Basic .NET 中的字符串操作

Visual Basic .NET 包含所有 C# 字符串运算符,还包括一些 C# 中没有的运算符。Visual Basic .NET 的运算符包括:

  • 使用 + 运算符只能并置 StringString,如 s + t。仅在两个操作数都是字符串时,此运算符才执行字符串并置。如果任一操作数不是字符串,Visual Basic .NET 会尝试将该字符串转换成 Double 并执行数值加法。因此,最好使用 & 运算符执行并置,如下所述。
  • 使用 & 运算符并置任意两个对象,如 s & "There"。如果 & 运算符的操作数不是字符串,它们会被转换成字符串。也就是说,您甚至可以并置两个数字的字符串表示。例如,使用语法 x & y 并置两个数字 xy 的字符串表示。在执行并置时首选此运算符,因为它很明确,始终执行并置。
  • 以下是全部比较运算符:=<>><>=<=,这些运算符执行二进制或文本(区分环境)比较,执行哪种比较取决于编译选项和 Option Compare 语句(如果有)。通过使用 Option Compare 设置比较类型,或者传递参数来指示使用哪种比较方法,也可以使用 StrComp 执行字符串比较。
  • 使用 Like 运算符可以确定字符串是否与使用简化的正则表达式语言描述的某个模式相匹配。而 System.Text.RegularExpressions.RegEx 支持的正则表达式语言具有更多功能,所以您可能更希望使用该 API。
  • Visual Basic .NET 不包含索引生成器,所以不能使用数组下标表示形式来访问字符串中的各个字符,但可以使用 Chars 属性获得字符串的单个字符,以及获得/设置 StringBuilder 的各个字符。

Visual Basic .NET 特有的字符串函数和语句

除了 Visual Basic .NET 语言提供的运算符以外,Visual Basic .NET 还包括一些特有的操作字符串的函数和语句,标准 .NET 框架库不包含这些内容。

如果您是一名 Visual Basic .NET 程序员,您可以选择使用 Visual Basic 特有版本,或标准的 .NET 框架版本。(但在某些方面,Visual Basic 提供了相应功能,而 .NET 框架并未提供。)Dr. GUI 强烈建议,只要能够,就始终坚持使用标准的 .NET 框架 API。这样可以提高您的技能,将来也可以将这些技能应用到其他语言。

另一方面,如果您要将早期 Visual Basic 代码迁移到 Visual Basic .NET,则使用 Visual Basic 特有版本是相当方便的,因为 Visual Basic 特有版本的语法和行为与早期 Visual Basic 中的语法和行为非常相似。

子字符串

如果您曾经使用 BASIC 编程,应该记得用于从字符串左侧、右侧和中间提取字符的 LeftRightMid 函数。您甚至可能还记得可以将 Mid 用在赋值运算符的左侧,以更改字符串中间的字符!

这些功能也都包含在 Visual Basic .NET 中,就像您记得的那样。有一点十分重要:Mid 中开始字符处的索引是从 1 开始的,而不是像 .NET 中的其他索引那样从 0 开始。(这是因为 BASIC 中的 MID$ 就是从 1 开始的,而 Mid 则是从前者继承来的。)

这是一个非常严重的问题,Dr. GUI 建议永远不要在新代码中使用 Mid。您可以在要迁移的能正常运行的代码中保留它们,但要做好心理准备,调试时在从 1 开始和从 0 开始的计数方法之间来回转换会让人无法忍受。

String.SubString 方法提供了重载功能,这是替代 LeftMid 函数的非常好的方法。对于 Left,只需使用 str.Substring(0, len);对于 Mid,可以使用 str.SubString(start, len),但要记住将起点调整一位,因为 Substring 的起点指向 0,而 Mid 的起点是指向 1 的。

Right 稍微复杂些,因为必须计算起始位置。例如:

Dim s As String = "Hello, world!"
Dim t1 As String = s.Substring(s.Length - subLen)
' 与以下语句相同
Dim t2 As String = Right(s, subLen)
Dim ret As String = String.Format("s0: {0}\nt1: {1}\nt2: {2}", s, t1, t2)
ret = ret.Replace("\n", Environment.NewLine)

注意此处 Dr. GUI 运用的小技巧。由于 Visual Basic .NET 没有换行符转义序列,他通过包含转义序列 ("\n") 解决了这一问题,然后在返回字符串之前,使用两个字符的 Environment.NewLine 字符串 ("\r\n") 替换每个转义序列。如果您希望输出的字符串与 C# 版本中的字符串完全相同,应使用 Chr(10)(而不是 Environment.NewLine)替换 "\n"。

替换 Mid 语句(用于替换字符串中的字符)比较复杂。您可以使用 String.Remove 删除要替换的字符,然后使用 String.Insert 插入新字符,但这样做需要创建两个新字符串。我们可以选择一种更有效的方法,即根据要修改的字符串的第一个子字符串创建 StringBuilder,然后将替换字符附加到它后面,再在其后附加原字符串的尾部,如:

Dim s As String = "123456xxx0"
' 子字符串从 0 开始
Dim sb As StringBuilder = New StringBuilder(s.Substring(0, 6))
sb.Append("789")
sb.Append(s.Substring(9, 1)) ' 从 0 开始的第 10 个字符
s = sb.ToString()
' 与从 1 开始的 Mid(s, 7, 3) = "789" 一样
Dim t As String = "123456xxx0"
Mid(t, 7, 3) = "789"
Dim ret As String = String.Format("s: {0}\nt: {1}", s, t)
ret = ret.Replace("\n", Environment.NewLine)

一个非常奇怪的函数

Visual Basic .NET 包含一个非常奇怪的函数,即 StrConv。它用一组位来指定各种转换方式,将一个字符串转换成另一个字符串。它使用文件系统规则或者区分环境的规则来转换大小写(小写单词的第一个字母大写),而且某些转换仅在您使用相应的东亚语言(如日语、中文或朝鲜语)时才有意义(才能执行):转换全/半角,以及转换片假名和平假名(仅日语)。按理应当包含简体中文和繁体中文之间的相互转换,但 Dr. GUI 得知此功能的使用效果并不好,所以不建议使用它。

对于大多数这种转换,.NET 框架 API 中并没有对应的功能。然而,这些转换是使用 Windows LCMapString API 完成的;如果您确实希望用 .NET 语言(而不是 Visual Basic)进行转换,可以使用平台 API 互操作来调用 LCMapString(就像 Visual Basic 所做的一样)。

与 .NET 框架功能匹配的函数

有许多 Visual Basic .NET 字符串函数与 .NET 框架功能匹配。下表列出了 Visual Basic .NET 函数及与其最接近的 .NET 框架功能。请注意,在某些情况下,以下功能并不完全等价。此外,还要注意,在许多情况下,.NET 框架版本都进行了重载,以便提供附加的功能。您需要阅读有关文档,以便充分利用这些功能。可以使用下表确定要查阅的适当方法。

表 2:Visual Basic 特有的字符串函数及与其最接近的标准 .NET 框架功能

Visual Basic .NET 函数 语义 最接近的 .NET 框架功能
StrReverse 反转字符串。 无 - 通过交换字符位置,使用 StringBuilder 和循环进行反转。
InStrInStrRev 查找子字符串的第一个/最后一个索引。 IndexOfLastIndexOf
LCaseUCase 转换为小写/大写。 ToLowerToUpper
FormatFormatCurrencyFormatNumberFormatPercent 将值的区分环境的格式转换为字符串表示。 obj.ToStringString.Format
StrVal 在数值和字符串之间转换,不区分环境。 以上任一项,指定 CultureInfo.Invariant
TrimLTrimRTrim(仅适用于空格) 去掉字符串两端的空格。 String.TrimString.TrimStartString.TrimEnd(不仅可以去掉空格,而且可以去掉其他字符。)
Len 返回字符串的长度。 String.Length
SpaceStrDup 使用重复的空格或其他字符创建字符串。 接受字符和计数的 String 构造函数。
Replace 替换字符串中的子字符串。 StringStringBuilder 中的 Replace
SplitJoin 在指定分隔符处断开字符串以创建字符串数组,或者从字符串数组创建分隔的字符串。 String.SplitString.Join
Filter 从包含子字符串的数组创建另一个字符串数组。 没有等价功能,需要编写 for-each 循环,可能需要将字符串放入可动态扩展的 ArrayList 中。
AscWChrW 在 Unicode 整数和单字符字符串之间来回转换。 char 强制转换为整数类型,或相反。
AscChr 在代码页整数和单字符字符串之间来回转换。 使用编码(稍后介绍)。
CStr 转换为字符串。 obj.ToString

使用标准 .NET 框架 API 的字符串

创建 String 对象

在这两种语言中,您都可以使用 new/New 语句创建 String 对象。大多数与 CLI 兼容的 String 构造函数可以不接受任何参数,也可以接受一个字符和一个整数,或者接受一个字符数组。所以,除非您有一个字符数组,否则还是需要使用其他方法来创建字符串。没有可以接受字符串的构造函数。但是,有一个构造函数接受一个 Char 和一个整数计数,它创建的字符串将包含重复了指定次数的指定字符。

最常用的方法是初始化一个字符串常值,并使用另一个字符串为其赋值:

[C#]
string s = "Hello, world!";
string t = s; // 不复制;s 和 t 引用同一个字符串
string ret = String.Format("s and t refer to same: {0}",
Object.ReferenceEquals(s, t));

[Visual Basic .NET]
Dim s as string = "Hello, world"
Dim t as string = s ' 不复制;s 和 t 引用同一个字符串
Dim ret as string = String.Format("s and t refer to same: {0}", s Is t)

何时克隆功能不执行克隆?

您是否希望两个独立的字符串包含相同的值?当然,通常您并不希望这样。为什么要浪费内存呢?而且,因为字符串是不可改变的,所以具有相同值的两个单独字符串并没有多大意义。

所以,虽然 String 实现了 IClonable,但 String.Clone 只返回对相同字符串的引用,而不进行克隆。

不过,所有功能都还在:如果您坚持希望拥有字符串的第二个副本,您可以使用静态方法 Copy。请注意,我们将通过两种方法来检查引用的等同性:第一种方法是调用 Object.ReferenceEquals,第二种方法是在 C# 中测试等同性之前将引用强制转换为 object,或者在 Visual Basic 中使用 Is 运算符。

[C#]
string s = "Hello";
string t = (string)s.Clone(); // 不复制;s 和 t 引用同一个字符串
string u = String.Copy(s); // 进行复制,s 和 u 引用不同的对象
string ret = String.Format("s same as t: {0}, s same as u: {1}",
Object.ReferenceEquals(s, t), (object)s == (object)u);

[Visual Basic .NET]
Dim s as string = "Hello"
Dim t as string = CStr(s.Clone()) ' 不复制;s 和 t 引用同一个字符串
Dim u as string = String.Copy(s) ' 进行复制,s 和 u 引用两个对象
Dim ret as string = String.Format("s same as t: {0}, s same as u: {1}", _
Object.ReferenceEquals(s, t), s Is u)

您也可以使用 String.CopyTo 方法,将一个字符串的全部或部分字符复制到字符数组中。(使用接受字符数组作为参数的字符串构造函数,根据字符数组或其中的一部分创建字符串。)

其他接口、属性和字段

正如我们所了解的,String 通过一种很奇怪但合理的方法实现了 ICloneable。它还实现了 IComparable。这就是说,您可以使用 IComparableCompareTo 方法(下文将详细说明)比较两个字符串。虽然 String 实现了 IConvertable,但您应当使用 Convert 类中的方法,利用 To... 方法将字符串转换为内置值类型。

String 还具有一些有趣的属性。通过访问字符串的 Length 属性(只读),可以知道任何字符串的长度(以字符数计,而不是以字节计)。而使用索引生成器属性 Chars(对应于 C# 索引生成器,只读),可以一次访问字符串的一个字符。

[C#]
string s = "Hello, world!";
StringBuilder sb = new StringBuilder(String.Format(
"\"Hello, world!\" is {0}\n", s.Length)); // 13
for (int i = 0; i < s.Length; i++)
sb.AppendFormat("{0} ", s[i]);

[Visual Basic .NET]
Dim s as string = "Hello, world!"
Dim sb as StringBuilder = new StringBuilder( _
String.Format("Length of ""Hello, world!"" is {0}", s.Length)) ' 13
sb.Append(Environment.NewLine)
Dim i as Integer
For i = 0 to s.Length - 1
sb.AppendFormat("{0} ", s.Chars(i))
Next

请注意,因为字符串字符的索引是从 0 开始的(即使在 Visual Basic .NET 中),我们应当注意正确编写循环(在 C# 中,对这一问题的处理是使用 < 运算符,而不是 <= 运算符)。

还有一个有趣的静态字段 Empty,它包含空字符串。这样,无论您使用哪种语言,都可以使用此字段来表示空字符串 ("")。

字符串等同/比较方法

可以通过许多方法比较两个字符串。对于等同和不等同比较,主要差别当然在于,是比较引用(两个字符串指向同一对象)还是比较值(两个字符串包含相同的字符)。

对于等同比较和关系比较,另一个主要差别在于,是使用当前环境的排序顺序,还是使用字符串中各个字符的原始序号值。(还有一个较小的差别,即比较时是否区分大小写。)比较的默认设置是使用它们所在线程的当前环境并区分大小写。通常,此设置就是您需要的设置。

== 运算符调用 String.Equals,后者将执行区分环境和大小写的比较。如果您希望使用 C# 比较引用,可以将两个字符串引用都强制转换为 Object,或者使用 Object.ReferenceEquals。在 Visual Basic .NET 中,可以如下所示使用 Is 运算符(在 Visual Basic 中),或者使用 Object.ReferenceEquals。请注意,在这两种语言中,都可以使用 Object.ReferenceEquals

[C#]
string s = "Hello", t = "there";
bool valueComp = (s == t); // 值比较
bool refComp1 = ((Object)s == (Object)t); // 引用比较
bool refComp2 = Object.ReferenceEquals(s, t); // 引用比较
string ret = String.Format("s == t: {0}, " +
"(object)s == (object)t: {1}, ObjectRefEq(s, t): {2}",
valueComp, refComp1, refComp2);

[Visual Basic .NET]
Dim s as string = "Hello"
Dim t as string = "there"
Dim valueComp as Boolean = s = t ' 值比较
Dim refComp1 as Boolean = s Is t ' 引用比较
Dim refComp2 as Boolean = Object.ReferenceEquals(s, t) ' 引用比较
Dim ret as string = _
String.Format("s = t: {0}, s Is t: {1}, ObRefEq(s, t): {2}", _
valueComp, refComp1, refComp2)

Visual Basic .NET 中的等同、不等同和比较运算符(=<>><>=<=)可以执行序号比较,也可以执行区分环境的比较,这取决于编译器选项和 Option Compare 语句。要执行引用比较,请使用 Is 运算符。Visual Basic .NET 也使用 Like 运算符执行字符串比较,它可以进行简单的模式匹配。

Equals 方法具有多种形式。有两种实例方法:一种接受 String 作为参数,另一种接受 Object(必须引用某个 String)。还有一种静态版本的 Equals,接受两个字符串。接受 Object 的版本被覆盖(最初在 Object 中定义);接受字符串的版本速度较快,因为无需进行转换;而静态版本能够处理所传递的 null/Nothing 而不会出现异常。

可以通过三种方法进行比较:CompareCompareOrdinalCompareTo。如果第一个字符串的值小于第二个字符串的值,这三种方法都返回一个负数;如果两个字符串的值相同,则返回 0;如果第一个字符串的值大于第二个字符串的值,则返回正数。

Compare 是一组重载的静态方法,默认情况下执行区分环境和大小写的比较。每个重载都接受至少两个字符串进行比较,也有的重载接受 Boolean(以指定区分大小写)和 Int32(以指定要比较的子字符串的索引和长度,而不比较整个字符串)。

CompareOrdinal 是一对重载的静态方法,用于比较两个字符串或其子字符串的序号。

CompareTo 是实现 IComparable 的实例方法。此方法将当前字符串与作为参数传递的字符串或对象进行比较,并且在比较时区分环境和大小写。

搜索字符串:EndsWith/StartsWith/IndexOf/LastIndexOf/Substring

可以通过多种方法找出字符串包含的内容。如果当前字符串以指定的字符串开始/结束,则 EndsWithStartsWith 将返回 true,否则返回 false

IndexOfLastIndexOf 都包含多个重载。每个重载都返回指定的字符串(或字符数组)在当前字符串(或其子字符串)中出现的第一个/最后一个位置。

Substring 类似于以前的 GW-BASIC MID$ 函数和 Visual Basic .NET Mid 函数,只是字符串的索引是从 0 开始的,而在 Mid/MID$ 中则从 1 开始。共有两个重载:一个创建并返回一个新字符串,其中包含从指定索引到字符串末尾的子字符串。另一个也创建并返回一个新字符串,但其中包含从指定索引处开始的指定长度的子字符串。

格式化

String 类具有一系列静态 Format 方法重载。所有重载都提供格式化功能,与调用 Console.WriteLine 时使用的功能相同。它们创建并返回新字符串,其中的格式指示符由其余参数的字符串表示替代。例如,可以使用:

[C#]
string ret = String.Format("The value is {0}", 5);
// "值为 5"(没有引号)

[Visual Basic .NET]
Dim ret as string = String.Format("The value is {0}", 5)
' "值为 5"(没有引号)

也可以使用其他方法确定数据格式,但涉及到许多区分环境的问题。很遗憾,限于本文篇幅,我们不能一一探讨这些问题。注意,.NET 框架在进行转换和格式化时通常区分环境,除非您将 CultureInfo.InvariantCulture 传递给格式化方法。默认情况下,Visual Basic .NET 内置方法通常不区分环境,请务必注意这一点!

分析

使用要转换到的目标类中的 Parse 方法,可以将一个字符串转换为多种其他类型。例如,要转换为整数,可以使用 Int32.Parse 方法。注意,还可以通过其他方法进行转换,例如使用 Convert 类中的方法;在某些情况下,也可以强制转换对象类型(在 C# 中),或者使用 Visual Basic 的 CTypeCStr 函数。

填充和剪裁

可以通过两种方法在字符串的左侧或右侧填充空格(或任何其他字符),这两种方法是 PadLeftPadRight 方法。还可以通过多种方法(TrimStartTrimEndTrim),在字符串的开头和/或结尾剪裁您指定的空格或任意字符。这些方法都将创建并返回新的字符串。

Insert/Remove/Replace/Concat

这些方法在某种程度上与 StringBuilder 中的方法类似,但前者在 String 中的重载要比 StringBuilder 中的少。有关这两者差异的详细信息,请参阅下文的“StringBuilder”一节。

Insert 在指定位置插入指定字符串,从而创建新字符串。Remove 从指定位置删除指定数量的字符,从而创建新字符串。Replace 使用另一个字符或字符串替换所有指定的字符或字符串,从而创建并返回新的字符串。而静态方法 Concat 的各个重载通过并置所传递的字符串和/或对象,来创建并返回新的字符串。

Split/Join

SplitJoin 这两种方法功能非常强大。Join 是一个静态方法,它使用指定的字符串作为分隔符,并置所传递的字符串数组中的部分或全部字符串,从而创建并返回字符串。Split 使用指定的一组分隔符字符,将一个字符串拆分成字符串数组,从而创建并返回字符串数组。

转换

String 没有可以直接访问的转换方法,但 Convert 类包含多种 static/Shared 的转换方法,大多数方法的形式为 To...,可以将字符串转换为某种内置类型。将字符串转换为内置类型的方法包括 ToBooleanToByteToCharToDecimalToDoubleToInt16ToInt32ToInt64ToSByteToSingleToStringToUInt16ToUInt32ToUInt64。还有一种方法能将字符串转换为 DateTime 对象,即 ToDateTimeFromBase64StringToBase64String 方法可以实现字节数组和 Base64 编码的字符串之间的转换(对通过文本协议传输二进制数据很有用)。

String 包含将字符串转换为字符数组的 ToCharArray 方法。还包含转换字符大小写的方法: ToUpperToLower

Intern/IsInterned

.NET 运行时在应用程序域(大致类似于进程)中保留了一个字符串常值池。当它将一个程序集(例如您的程序)加载到应用程序域中时,将该程序集的字符串常值与应用程序域的字符串常值池合并,从而避免了字符串常值的重复。(这样做不会改变程序的结果,因为字符串是不可改变的。)实际上,所有字符串常值都被保留,这样,因为每个字符串常值只有一个副本,您就可以正确地对字符串常值进行引用比较。

然而,如果您自己生成一个字符串(例如使用创建新字符串的 String 方法之一,或者使用 StringBuilder),该字符串将与字符串常值池中具有相同值的字符串(假定存在)不同,所以引用比较将失败。这个问题可能很严重,因为引用比较比值比较快得多,它只需比较地址即可。(所以如果您确实需要快速运行的代码,则在确保能够进行引用比较的前提下,就需要使用引用比较!)

如果要将您的字符串添加到常值池中,可以使用静态方法 Intern,该方法将字符串添加到常值池中(如果尚未添加到池中)并返回对常值池字符串的引用。例如:

[C#]
string s2 = new StringBuilder().Append("Foo").Append("Bar").ToString();
string s3 =String.Intern(s2); // 返回对常值池的引用
string s = "FooBar"; // 始终位于常值池中
StringBuilder sb = new StringBuilder();
sb.Append(Object.ReferenceEquals(s2, s)); // false:不同
sb.Append(", ");
sb.Append(Object.ReferenceEquals(s3, s)); // true:相同

[Visual Basic .NET]
Dim s2 as string = New _
StringBuilder().Append("Foo").Append("Bar").ToString()
Dim s3 as string = String.Intern(s2) ' 返回对常值池的引用
Dim s as string = "FooBar" ' 始终位于常值池中
Dim sb as new StringBuilder()
sb.Append(s2 Is s) ' false:不同
sb.Append(", ")
sb.Append(s3 Is s) ' true:相同

您还可以使用静态 IsInterned 方法,检查字符串是否已经位于池中;如果是,则返回 true

GetHashCode

您可能记得,上一次我们讨论了这一问题,即如果覆盖 Equals,则也应当覆盖 GetHashCode。在 String 中,Equals 被覆盖,所以 GetHashCode 也被覆盖,这提供了良好的散列和性能。

StringBuilder

您可能已经注意到,String 中的许多方法都会创建并返回新字符串。如您所料,分配和废弃大量字符串代价是很高的。

通常,如果您要对特定的字符串执行一个需要创建新字符串的字符串操作,可以放心使用 String 中的相应方法(或适当的 Visual Basic .NET 函数)。但是,如果您要执行多个这种操作,而且 System.Text.Stringbuilder 中提供了所需的操作,则应根据您的字符串创建一个 StringBuilder,然后在该 StringBuilder 上执行多个操作,最后在 StringBuilder 上调用 ToString 以返回结果字符串。请参阅前面的示例,在该示例中,所有操作都在一行中完成。

通常的做法是在 StringBuilder 中生成一个字符串,对其进行操作,然后再将其转换成字符串。请注意,调用 ToString 并不会真正复制字符串,除非您以后又修改了同一个 StringBuilder 对象,所以它的效率是很高的,使用 StringBuilder 的系统开销只是一个复制操作,而不是两个。这就是我们为什么使用一个操作规则的原因。对于两个或多个操作,使用 StringBuilder 至少能够获得同样的效果。

如果可用重载比 String 中更有限的重载更易使用,您也可以使用 StringBuilder

要使用 StringBuilder,您需要在程序文件的开始处包括 using System.Text;(在 Visual Basic 中是 Imports System.Text)。

StringBuilder 具有多个构造函数,可以从字符串初始化对象并设置其容量和最大容量。(默认的最大容量约为 20 亿个字符,所以,尽管设置较大的值也不会出问题,但设置较小的值还是比较合理的。)对象的容量可以根据需要调高,只要不超过最大容量即可。构造 StringBuilder 之后,就不能再调整其最大容量。

共有以下四个属性:Capacity(可读写)、MaxCapacity(只读)、Length(字符串的当前长度,可以设置得更短或更长)和 Chars(索引生成器,用于读写单个字符)。如果容量不够大,可以使用 EnsureCapacity 方法增加容量。

使用 Append 方法,可以将字符串或任意类型(包括许多重载)附加到 StringBuilder 末尾。如果传递其他类型,将调用其 ToString 方法,并将结果附加到 StringBuilder

使用 AppendFormat 方法,可以格式化附加到 StringBuilder 的字符串。其格式化方法与 String.FormatConsole.WriteLine 相同。

使用 Insert 的许多重载,您都可以在 StringBuilder 中的任意位置插入字符串(可能通过调用参数的 ToString 方法计算得出)。

使用 Remove,可以删除从任意位置开始任意数量的字符。

使用 Replace,可以替换单个字符,或者如前文所述替换子字符串。

最后,使用 ToString 的重载,可以根据 StringBuilder(或其指定的子字符串)创建并返回新的 String 对象。

System.Text 编码器和解码器

您一定已经注意到,.NET 框架程序中的所有字符串都存储为 16 位 Unicode。但并非每个人都使用 Unicode,所以有时必须将某些其他字符编码转换为 Unicode,或者将 Unicode 转换为其他字符编码。

.NET 框架提供了多种类可用于编码(将 Unicode 字符转换成另一种编码的字节块)和解码(将某些编码的字节块转换成 Unicode 字符)。

针对支持的每种编码都有一个类:ASCIIEncodingCodePageEncodingUnicodeEncoding(用于将 big-endian 转换为 little-endian)、UTF7EncodingUTF8Encoding

每种类都包含用于编码的方法(如 GetBytes)和解码的方法(如 GetChars),可以一次编码和解码一个数组。此外,每个类都支持 GetEncoderGetDecoder,它们返回能够维护移位状态的编码器和解码器,所以可以用于流和块。

正则表达式

您可能已经注意到,Visual Basic .NET 使用 Like 语句只能支持有限的正则表达式匹配。此外,.NET 框架在 System.Text.RegularExpressions 命名空间中支持非常复杂的正则表达式处理。此处的关键类是 System.Text.RegularExpressions.RegEx,它代表已编译的正则表达式,并提供使用该表达式的方法,可用于在字符串中查找和替换正则表达式,以及将一个字符串拆分成字符串数组。有关正则表达式的文档现在还很少,所以我们暂不深入讨论。在今后的专栏中可能会深入介绍。

试一试!

组成 .NET 框架学习小组

热心的博士希望您不仅自己在 .NET 中漫游,而且还要和其他人合作。这样您可以获得更多乐趣,并能保证学到更多的东西。

应进行的尝试

请看一看源文件。将其复制到 Visual Studio 中并自行修改。

试着处理某些字符串,或者字符串数组。

尝试使用 StringBuilder 完成一些字符串处理。并使用 String 的某些高级功能分析字符串或命令行。

如果您想追求更大的乐趣,可以尝试编码和解码不同的字符集和代码页。如果您想扮演开拓者的角色,可以尝试处理某些正则表达式类。

本期主题和下期主题

这次我们讨论了字符串。下次我们将讨论数组。

posted on 2005-09-05 10:36  行万里路 责任 创新 执着  阅读(1046)  评论(0编辑  收藏  举报