字符、字符串和文本处理

1.1字符

在.NET Framework中,字符都是用16位Unicode编码(UTF-16)的(编译时用UTF-16编码成2进制存到硬盘,程序运行时再用utf-16解码显示代码中的字符串,在内存中相应的字节流就是用UTF-16编码过的),也就是说所有字符都是占2个字节16位,这简化了国际化应用程序的开发。Unicode字符集有很多种编码方案,常用的有:

UTF-16:所有字符被编码成2个字节

UTF-8:十进制小于128的字符被编码成1个字节(可表示欧美地区使用的字符),128~2047的字符被编码成2个字节(可表示欧洲和中东语言),大于2047的字符被编码成3个字节(可表示东亚地区的语言)

UTF-32:所有字符都被编码成4个字节

Unicode字符集还有个ASCII编码方案,这种编码只能将小于128的16位字符转换成单字节,而其他超过127的字符都会丢失。

GB2312等其他字符集(这些字符集可能只有一种同名编码方案)

针对Char的一个实例,可以调用Char类型的静态方法GetUnicodeCategory,这个方法返回的是System.Globalization.UnicodeCategory枚举类型的一个值。这个值指出该字符是控制字符、货币符号、小写字母、大写字母、标点符号、数字符号 还是其他Unicode标准定义的符号。 其他一些静态方法如IsDigit、IsLetter、IsUpper、IsControl、IsSymol等都在内部调用了GetUnicodeCategory,并简单返回true或false。注意,所以这些方法要么获取单个字符作为参数,要么获取一个String以及目标字符在这个String中的索引作为参数。

另外,可以调用静态方法ToLowerInvariant或者ToUpperInvariant以一种忽略语言文化的方式,将一个字符转化为小写或大写。如果调用ToLower和ToUpper方法,在转换时要使用与线程相关的语言文化信息,语言文化信息是这两个方法在内部查询System.Threading.Thread类的静态CurrentCulture属性来获得的。还可以向这些方法传递CultureInfo类的一个实例来具体指定一种语言文化。ToLower和ToUpper之所以需要语言文化信息,是因为字母的大小写转换是依赖于语言文化操作的。

可以使用三种技术实现各种数值类型与Char实例的相互转换,下面按照优先顺序列出这些技术。

 

*转型(强制类型转换)要将一个Char转换成一个数值(如Int32),最简单的方法是强制类型转换。在三种技术中,这种技术效率最高,因为编译器会生成IL(Intermediate Language中间语言)指令来执行转换,不必调用任何方法。

*使用Convert类型 System.Convert类型提供了几个静态方法来实现Char和数值类型的相互转换。这些方法都以checked方式来执行转换,因此一旦发现转换造成数据丢失,就会抛出一个OverflowException异常。

*使用IConvertible接口 Char类型和FCL中的所有数值类型都实现了IConvertible接口。该接口定义了像ToUInt16和ToChar这样的方法。但是这种技术效率最差,因为在值类型上调用一个接口方法,要求对实例进行装箱(Char和所有数值类型都是值类型)。如果某个类型不能转换(比如Char转换成Boolean),或者转换造成数据的丢失,IConvertible的方法会抛出一个System.InvalidCastException异常。

以下代码简单演示如何使用这三种技术

 1             Char c;

 2             Int32 n;

 3 

 4             //使用C#强制转换技术实现数字与字符的相互转型

 5             c = (Char) 65;

 6             Console.WriteLine(c);       //显示"A"

 7 

 8             n = (Int32) c;

 9             Console.WriteLine(n);       //显示"65"

10 

11             c = unchecked((Char) (65536 + 65));

12             Console.WriteLine(c);       //显示"A"

13 

14             //使用Convert实现数字与字符的相互转换

15             c = Convert.ToChar(65);

16             Console.WriteLine(c);       //显示"A"

17 

18             n = Convert.ToInt32(c);

19             Console.WriteLine(n);       //显示"65"

20 

21             //演示Convert的范围检查

22             try

23             {

24                 c = Convert.ToChar(70000);  //对16位来说太大

25                 Console.WriteLine(c);   // 不执行

26             }

27             catch (OverflowException)

28             {

29                 

30                 Console.WriteLine("Can't 70000 to a char!");

31             }

32 

33             //使用IConvertible实现数字与字符的相互转换

34             c = ((IConvertible) 65).ToChar(null);

35             Console.WriteLine(c);       //显示"A"

36 

37             n = ((IConvertible) c).ToInt32(null);

38             Console.WriteLine(n);       //显示"65"
View Code

1.2 System.String类型

1.2.1 构造字符串

一个String代表一个不可变的顺序字符集。String类型直接派生自Object,所以它是一个引用类型。因此String对象总是存在于堆上,永远不会跑到线程栈。许多编程语言都将String视为一个基元类型----可以再源代码中直接表示文本常量字符串(string s="hi"); 编译器将这些文本常量字符串放到模块的元数据中,并在运行时加载和引用它们。

在C#中,不能使用new操作符从一个文本常量字符串构造一个String对象。

对于换行符、回车符和退格符这样的特殊字符,C#采用的是C/C++开发人员熟悉的转义机制:

1  // 包含回车符和换行符的字符串

2 string s="hi\r\nthere";

3 

4 //NewLine是System.Environment类型定义的一个属性,NewLine属性是依赖于平台的,它在任何平台上都能正确工作,建议使用这种方式

5 string s="hi"+Environment.NewLine+"there";

可以使用C#的+操作符将几个字符串连接成一个,如下所示:

对于如下由好几个文本常量字符串组成的字符串:

string s="hi"+" "+"there";



//注意:编译器会在编译时连接它们,最终只会将一个字符串放到模块的元数据中
对于如下由好几个非文本常量字符串组成的字符串:
string s1="hi"; string s2="there"; string s=s1+s2; 



//注意:对非文本常量字符串使用+操作符,连接会在运行时进行。
若要在运行时将几个字符串连接到一起,应避免使用+操作符,因为它会在堆上创建多个字符串对象,而堆是需要回收的,从而影响性能。相反,应尽量使用System.Text.StringBuilder类型
 
  

C#还提供了逐字字符串,通常用于指定文件或目录的路径,或与正则表达式配合使用。采取这种方式,引号之间的所有字符都会被视为字符串的一部分:

1 //指定应用程序路径

2 string file="C:\\windows\\System32\\Notepad.exe";

3 

4 //使用逐字字符串来指定应用程序路径

5 string file=@"C:\windows\System32\Notepad.exe";

在字符串前添加@符号,使编译器知道字符串是一个逐字字符串,这告诉编译器将反斜杠视为文本常量,而不是转义符,使文件路径在源代码中更易读。

1.2.2 字符串是不可变的

string对象最重要的一个事实就是,它是不可变的,也就是说字符串一经创建便不能更改,不能变长,变短或修改其中任何字符。

所以允许对一个字符串进行各种操作而不实质的改变字符串:

1 string s="hiworld"; if(s.ToUperInvariant().Substring(0,2).EndsWith("EXE"))

2 {

3     ...

4 }

在此,ToUperInvariant()返回一个新的字符串,它没有修改s的字符,然后Substring(0,2)在ToUperInvariant()返回的新字符串的基础上又返回一个新字符串。 ToUperInvariant和Substring创建的两个临时字符串不会由应用程序代码长久的引用,垃圾回收器会在下次回收时回收它们的内存,如果执行大量的字符串操作,会在堆上创建大量的string对象,造成频繁的垃圾回收,从而损害应用程序的性能,要想高效率地执行大量字符串操作,请用StringBuilder类。

使字符串不可变,还意味着在操纵或访问一个字符串时不会发生线程同步问题。

你可能感兴趣的:(字符串)