Microsoft.Net框架程序设计学习笔记(17):字符串驻留
时间:2011-03-28 来源:辛勤的代码工
关于回车、换行
String s = "Hi\r\nthere.";
以上代码将回车、换行字符硬编码到字符串中,但更加好的做法是使用System.Enviroment类型提供的一个名为NewLine的只读属性。
示例代码:
String s = "Hi" + Enviroment.NewLine + "there.";
这样做的好处是,NewLine属性依赖于特定平台,根据底层平台的不同,它的返回值也不同。在Windows系统上运行时,该属性返回的字符串为"\r\n"。如果将CLR移植到UNIX系统上,NewLine属性返回一个仅含"\n"的字符串。这样,使用NewLine属性保证它在任何平台上都能正确运行。
字符串的恒定性
String对象最重要的特性是其恒定性。一个字符串一旦被创建,我们就不可能再将其变长、变短、或者改变其中的任何的字符。
比较字符串
String.CompareOrdinal方法比较字符串速度较快,因为它仅仅比较字符串中字符的码值。
String.Equals方法在内部使用到了CompareOrdinal方法。它首先检查两个引用是否指向同一个对象,如果是,则返回true。否则再调用CompareOrdinal。在使用字符串驻留机制时,比较引用会极大地提高性能。
String的==和!=操作符在内部是通过调用String的静态Equals方法实现。
要在逻辑上判断两个字串是否相等,我们应该使用String.Compare方法。该方法在内部使用了特定语言文化的排序表,该排序表也是.Net框架的一部分。
字符串驻留
String s = "Hello";
Console.WriteLine(Object.ReferenceEquals("Hello", s));
这段代码显示的是"True"还是"False"呢?可能大家认为是返回"False",毕竟,我们有两个"Hello"字符串,且ReferenceEuqals只有在两个引用都指向同一对象时才返回true。然而,这段代码显示的是"True"。
当CLR初始化时,它会创建一个内部的散列表,其中的键为字符串,值为指向托管堆中字符串对象的引用。刚开始,该表为空。当JIT编译器编译方法要创建字符串时,它会在散列表中查找每一个文本常量字串。以上面的代码为例,编译器首先会查找第一个"Hello"字符串,且因为没有找到,便会在托管堆中构造一个新的String对象(指向该字符串),然后将"Hello"字串和指向该对象的引用添加到散列表中。接着,JIT编译器在散列表中查找第二个"Hello"字符串,这次因为会找到该字符串,所以不执行任何操作。
当代码执行时,它会在第一行发现需要一个"Hello"字串引用。于是,CLR在其内部的散列表中查找"Hello",且会找到它,这样指向先前创建的String对象的引用被保存在变量s中。当执行到第二行代码时,CLR会再次在散列表中查找"Hello",且仍会找到它。这样,指向同一个String对象的引用会被传递给Object的静态方法ReferenceEquals作为第一个参数,自然该方法返回true。
当一个引用字符串的方法被JIT编译时,所有嵌入在源代码中的文本常量字符串总会被添加到CLR内部的散列表中。但是,运行时动态创建的字符串呢?
String s1 = "Hello";
String s2 = "Hel";
String s3 = s2 + "lo";
s3 = Strng.Intern(s3);
Console.WriteLine(Object.ReferenceEquals(s1, s3));
Console.WriteLine(s1.Equals(s3));
s3是一个动态创建的字符串,其值为"Hello",与s1相同,但由于其值只有在运行时才能确定,所以它并没有在JIT时被添加到CLR内部散列表中。因此,ReferenceEquals方法返回"false",而Equals方法返回"true"。
String类型提供两个静态方法允许我们在运行时将字串加入到散列表中。
public static string Intern(string str);
public static string IsInterned(string str);
第一个方法接受一个string参数,然后在CLR内部散列表中查找它。如果能找到该字符串,Intern将返回已存在的String对象的引用。如未找到,该字符串将被添加到CLR内部散列表中,Intern亦返回该String对象的引用。
再考虑以下代码:
String s1 = "Hello";
String s2 = "Hel";
String s3 = s2 + "lo";
s3 = String.Intern(s3);
Console.WriteLine(Object.ReferenceEquals(s1, s3));
Console.WriteLine(s1.Equals(s3));
此时,ReferenceEquals方法返回"true",而Equals方法返回"true"。
IsInterned方法与Interned方法区别是,当散列表中不包含指定字符串对象时,返回null,不会将该字符串添加到散列表中。
注意:垃圾收集器不会释放CLR内部的散列表中引用的字符串对象,这些String对象的引用一直被散列表保存着。只有当进程中所有的应用程序域都不再引用这些字符串对象时,它们才会被释放。这是因为字符串驻留是按进程为单位进行的,也就是说一个字符串对象可以被同一个进程中的多个应用程序域所访问,也为我们的应用程序节省不少内存。
实际上,C#编译器就使用IsInterned方法对switch/case语句进行了性能优化。