在任何应用程序中,Sysytem.String都是用得最多的类型之一。
一个string代表一个不可变(immutable)的顺序字符集。String类型直接派生自object类型,它是一个引用类型,不会发生装箱的操作。
一、构造字符串
C#中将String视作一个基元的类型——也就是说编译器允许直接在源代码中直接表示文本常量(literal)字符串,把在放到元数据中,并在运行时直接加载它们。
注意:在C#中不能用new操作符从文本常量字符串构造一个String对象。
string s=new String("hello"); //错误
string s=“hello”; //正确,必须使用简化过的语法
在文本常量字符串可以使用换行符,回车符,和退格符等这样的字符串。
但是不建议这样做,尽量使用System.Environment.NewLine属性(代替回车和换行符),该属性会返回一个由回车和换行构成的一个字符串,它会依据底层平台返回恰当的字符串,在任何平台上都能工作(比如将公共语言结构移植到UNix上)
C#中还提供了一种特殊字符串的声明方式。(逐字字符串)。以下代码展示了如何使用和不使用逐字字符串来声明一个字符串
//指定应用程序路径 String file="C:\\Windows\\System32\\Notepad.exe"; //使用逐字字符串来指定应用路径
String file=@"C:\Windows\System32\Notepad.exe";//更易于阅读 //在程序中上述两种代码都可以,他们在元数据中生成完全一致的字符串,但第二种因为不使用 //转义字符,使文件路径在源代码中更易于阅读。
二、字符串是不可变的
字符串一经创建就不可能更改,不能变长变短或者修改任何字符。使字符串不可变有几个好处。
首先,它允许在一个字符串执行各种操作,而不实际更改字符串。
如 s.ToUpperInvariant().SubString(10,21).EndWith("EXE"),
ToUpperInvariant和substring都会创建两个临时字符串,但是频繁执行这样的操作,会创建大量的string对象,造成频繁的垃圾回收,从而影响性能。
要想高效执行字符串操作,请使用StringBuilder类
第二,使用字符串不可变意味着在操作或者访问一个字符串时不会发生线程同步问题
除此之外,CLR 可通过一个string对象共享多个完全一致的string内容(字符串留用)。这样能减少系统中的字符串数量,从而节省内存。
三、比较字符串
一般会处于两个原因来比较字符串,判断相等性或者对字符串进行排序(通常是为了显示给用户看)。
许多程序都将字符串用于内部编程的目的,比如路径名,文件名,环境变量,注册表值,xml标记及其属性等等,这些字符串通常只在程序内部使用,不会向用户显示。
出于编程目的而比较字串时,应该总是使用StringComparison.Ordinal或者StringComparison.OrdialLgnoreCase,这是比较字符串最快的一种方式,因为在执行比较时不需要考虑文化信息。
另一方面,如果想以一种语言文化正确的方式来比较字符串(通常为了向用户显示),应该使用StringComnparison.CurrentCulture或者StringComparision.CurrentCulttureLgnoreCase。
有的时候,以一种语言文化正确方式来比较字符串时,会希望指定另一种语言文化,而不是使用与调用线程关联的语言文化。在这种情况下,可以使用框架列出的StartWith,EndWith和Compare方法重载版本,他们都接受Boolean和CultureInfo参数。
现在,我们将简要说说如何执行在语言文化上的正确的比较。
.NetFramework使用System.Globalization.CultureInfo来表示"语言/国家"对。在CLR中,每个线程都关联了两个特殊属性,每一个属性都引用了CultureInfo对象。
该属性用于获取要向用户展示的资源
它对GUI或者WEB窗体应用程序最有用,因为它标识了在显示UI元素时(比如标签和按钮)应使用的语言。默认情况下,在创建一个线程时,这个线程属性会被设置成一个CultureInfo对象,后者标识了正在运行应用程序的Windows版本所应用的语言。
该属性用于CurrentUiCulture属性不适用的场合,其中包括数字和日期格式化,字符串大小写以及字符串比较。
格式化时要同时用到CultureInfo对象的“语言”和“国家”的部分。创建线程时,这个线程属性被默认设为一个CultureInfo对象。
在许多计算机上,线程的CurrentUiCulture属性和CurrentCulture属性都被设为同一个CultureInfo对象。也就是他们使用相同的国家和信息。然而也可将它们设为不同的对象。
比如:在美国运行的一个应用程序可能需要西班牙语来显示它所的菜单项以及其他GUI元素,同时仍然要正确显示美国的货币和日期格式。
using System; using System.Globalization; public class CLRviaCSharp_16 { static void Main(string[] args) { string s1 = "中文"; string s2 = "日本語"; CompareInfo compareInfo = CompareInfo.GetCompareInfo("ja-JP"); Console.WriteLine(compareInfo.Compare(s1, s2)); compareInfo = CompareInfo.GetCompareInfo("zh-CN"); Console.WriteLine(compareInfo.Compare(s1, s2)); Console.ReadKey(true); } }
注意:源代码文件不要用ANSI格式保存,否则一些字符会丢失。要在VS中保存这个,请选择“另存为”——保存右侧的向下箭头,选择“编码保存”,并选择“Unicode(UTF-8带签名)代码页65001”。
MiscroSoft C#编译器可以成功解析用这个代码页的保存的源代码文件。
四、字符串留用
如果应用程序经常对字符串进行区分大小写的,序号式的比较,或者事先知道许多字符串对象都有相同的值,就可以利用CLR的字符串留用机制来显著提高性能。CLR初始化时会创建一个内部哈希表。在这个表中,键(Key) 是字符中,而值(value)是对托管堆中的string对象的引用。哈希表开始是空的(理由如此)。String提供了两个方法,便于你访问这个内部哈希表:
public static String Intern(String str);
public static String IsInterned(string str);
一个程序加载时时,CLR默认会留用程序集元数据中描述的所有文本常量字符串。Miscrosoft知道可能因为额外的哈希表查找造成性能的下降,所以现在能禁用这个“特性”。
以下代码演示了字符串留用
String s1="hello"; String s2="hello"; console.writeLine(Object.ReferenceEquals(s1,s2));//应该显示false s1=String.Intern(s1); s2=String.Intern(s2); console.writeLine(Object.ReferenceEquals(s1,s2));//显示true
字符串留用虽然很有用,但有时也必须谨慎。使用留用方法时,大多都要花时间对其留用,如果应用程序使用调用多次这样的方法,那么该方法真的会对性能和内存利用造成不良影响,应用程序总体性能可能变得更慢。
五、字符串池
编译源代码时,编译器必须处理每个常量字符串,并在托管堆模块的元数据中都嵌入字符串。如果同一个文本常量字符串在源代码中多次出现,将所有这些字符串都嵌入元数据中,会徒然增大最终生成的文件。
为了解决这个问题,许多编译器(包括C#编译器)都只在模块元数据中将文本常量字符串写入一次。这种技术就是“字符串池”。虽然如此,字符串仍然是提升字符串性能的一种有效方式,而且你应该意识到它存在。
六、字符串的字符和文本元素
虽然字符串比较对于排序或者测试相等非常有用,但是有时我们只想检查一下字符串的字符。String类型提供了几个属性和方法:包括Length, Chars(在C#中是一个索引器), GetEnumerator, ToCharArray, Contains, IndexOf, LastIndexOf,IndexOfAny 和LastIndexOfAny。
实际上,一个String.Char代表的是一个16位unicode码值,而且这个码值并不一定等于一个抽象的Unicde字符。例如,有的抽象的Unicode字符是两个码值班的组合。如果合并到一起,两个码值就构成了一个单独的抽象字符,或者称为文本元素。
除此之外,有的unicode文本元素要求用户16位的值来表示它们。这种文本元素是用两个16位值来表示的。第一个码值称为高位代理项(high Surrogate),第二个码值称为低位代理项(low surrogate)。在代理项目的帮助下,Unicode可以表示100万个以上不同的字符。
为了正确处理文本元素,应当使用System.Globalization.StringInfo类型。使用这个类型最简单的方式就是构造他的一个实例,向它的构造器传递一个字符串。然后,可以查寻StringInfo的LengthInTextElement属性来了解字符串有多少个文本元素。然后可以调用 StringInfo的SubStringByTextElements方法来提取所有文本元素,或者提取指定数量的,连续的文本元素。
除此之外,StringInfo类还提供了一个静态方法GetTextElementEnumerator,这返回一个String.Globalization.TextElementEnumerator对象,允许你枚举字符串中包含的所胡抽象Unicode字符。最后可由StringInfo的静态方法ParseCombiningCharacters来返回一个由Int32值构成的数组。从字符组的长度就可以知道字符包含了多少个文本元素。每个数组元素都是字符串中的一个索引,该索引指向一个文本元素的起始码值。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Globalization; using System.Windows.Forms; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { string s = "a\u0304\u0308bc\u0327"; SubStringByTextElement(s); EnumTextElement(s); EnumTextElementIndexes(s); } private static void SubStringByTextElement(string s) { string output = String.Empty; StringInfo si = new StringInfo(s); for (Int32 Element = 0; Element < si.LengthInTextElements; Element++) { output += String.Format("Text Element {0} is '{1}'{2}", Element, si.SubstringByTextElements(Element, 1), Environment.NewLine); } MessageBox.Show(output, "Result of subStringByTextElement"); } private static void EnumTextElement(string s) { string output = String.Empty; TextElementEnumerator charEnum = StringInfo.GetTextElementEnumerator(s); while (charEnum.MoveNext()) { output +=String.Format("charcter at index {0} is '{1}'{2}",charEnum.ElementIndex,charEnum.GetTextElement(),Environment.NewLine); } MessageBox.Show(output, "Result of GetTextElementEnumrator"); } private static void EnumTextElementIndexes(string s) { string output = String.Empty; Int32[] textElementindex = StringInfo.ParseCombiningCharacters(s); for (Int32 i = 0; i < textElementindex.Length; i++) { output += String.Format("character {0} starts at index '{1}'{2}", i, textElementindex[i], Environment.NewLine); } MessageBox.Show(output, "Result of subStringByTextElement"); } } }
七、字符串的其他操作
还可以利用String类型提供的一些方法来复制怀个字符串或者复制它的一部分。
clone | 实例 |
copy | 静态 |
CopyTo | 实例 |
SubString | 实例 |
ToString | 实例 |
除了这些方法外,string还提供了用于处理字符串的静态或者实例方法。比如Insert, Remove,PadLeft,Replace,Split,Join,ToLower, ToUpper, Trim,Concat,Format等等。
提醒注意的是:使用这些方法,它们返回的都是的一个新的字符串对象(而不是原来的)。 由于字符串是不可变的,一经创建,就不可更改。