字符串具有不可变性,程序中对于同一个字符串的大量修改或者多个引用赋值同一字符串在理论上会产生大量的临时字符串对象,这会极大地降低系统的性能。对于前者,可以使用StringBuilder类型解决,而后者,.NET则提供了另一种不透明的机制来优化,这就是传说中的字符串驻留池机制。
使用了字符串驻留池机制之后,当CLR启动时,会在内部创建一个容器,该容器内部维持了一个类似于key-value对的数据结构,其中key是字符串的内容,而value则是字符串在托管堆上的引用(也可以理解为指针或地址)。当一个新的字符串对象需要分配时,CLR首先监测内部容器中是否已经存在该字符串对象,如果已经包含则直接返回已经存在的字符串对象引用;如果不存在,则新分配一个字符串对象,同时把其添加到内部容器中取。But,这里有一个例外,就是当程序员用new关键字显示地申明新分配一个字符串对象时,该机制将不会起作用。.
从上面的描述中,我们可以看到字符串驻留池的本质是一个缓存,内部维持了一个键为字符串内容,值为该字符串在堆中的引用地址的键值对数据结构。我们可以通过下面一段代码来加深对于字符串驻留池的理解:
public class Program
{
public static void Main(string[] args)
{
// 01.两个字符串对象,理论上引用应该不相等
// 但是由于字符串池机制,二者指向了同一对象
string a = "abcde";
string b = "abcde";
Console.WriteLine(object.ReferenceEquals(a, b));
// 02.由于编译器的优化,所以下面这个c仍然指向了同一引用地址
string c = "a" + "bc" + "de";
Console.WriteLine(object.ReferenceEquals(a, c));
// 03.显示地使用new来分配内存,这时候字符串池不起作用
char[] arr = { 'a', 'b', 'c', 'd', 'e' };
string d = new string(arr);
Console.WriteLine(object.ReferenceEquals(a, d));
Console.ReadKey();
}
}
在上述代码中,由于字符串驻留池机制的使用,变量a、b、c都指向了同一个字符串实例对象,而d则使用了new关键字显示申明,因此字符串驻留池并没有对其起作用,其运行结果如下图所示:
字符串驻留池的设计本意是为了改善程序的性能,因此在C#中默认是打开了字符串驻留池机制,But,.NET也为我们提供了字符串驻留池的开关接口,如果程序集标记了一个System.Runtime.CompilerServices.CompilationRelaxationsAttribute特性,并且指定了一个System.Runtime.CompilerServices.CompilationRelaxations.NoStringInterning标志,那么CLR不会采用字符串驻留池机制,其代码声明如下所示,但是我添加后一直没有尝试成功,你可以试试:
[assembly: System.Runtime.CompilerServices.CompilationRelaxations(System.Runtime.CompilerServices.CompilationRelaxations.NoStringInterning)]