|
关于字符串的驻留的机制,对于那些了解它的人肯定会认为很简单,但是我相信会有很大一部分人对它存在迷惑。在开始关于字符串的驻留之前,先给出一个有趣的Sample:
Code Snip:
static void Main(string[] args)
 {
string str1 = ABCD1234;
string str2 = ABCD1234;
string str3 = ABCD;
string str4 = 1234;
string str5 = ABCD + 1234;
string str6 = ABCD + str4;
string str7 = str3 + str4;

Console.WriteLine(string str1 = \ABCD1234\;);
Console.WriteLine(string str2 = \ABCD1234\;);
Console.WriteLine(string str3 = \ABCD\;);
Console.WriteLine(string str4 = \1234\;);
Console.WriteLine(string str5 = \ABCD\ + \1234\;);
Console.WriteLine(string str6 = \ABCD\ + str4;);
Console.WriteLine(string str7 = str3 + str4;);

Console.WriteLine(\nobject.ReferenceEquals(str1, str2) = {0}, object.ReferenceEquals(str1, str2));
Console.WriteLine(object.ReferenceEquals(str1, \ABCD1234\) = {0}, object.ReferenceEquals(str1, ABCD1234));

Console.WriteLine(\nobject.ReferenceEquals(str1, str5) = {0}, object.ReferenceEquals(str1, str5));
Console.WriteLine(object.ReferenceEquals(str1, str6) = {0}, object.ReferenceEquals(str1, str6));
Console.WriteLine(object.ReferenceEquals(str1, str7) = {0}, object.ReferenceEquals(str1, str7));

Console.WriteLine(\nobject.ReferenceEquals(str1, string.Intern(str6)) = {0}, object.ReferenceEquals(str1, string.Intern(str6)));
Console.WriteLine(object.ReferenceEquals(str1, string.Intern(str7)) = {0}, object.ReferenceEquals(str1, string.Intern(str7)));
}
下边是输出的结果:
 接下来我们来逐句地分析这段代码:
首先我们创建了两个完全相同的字符串(ABCD1234),并将他们分别赋予了两个字符创变量——str1和str2。然后把它们传给了object.ReferenceEquals。我们知道object.ReferenceEquals是用于确定两个变量是否具有相同的引用——换句话说,当两个变量引用的是同一块托管推的内存快的时候,返回True,否则返回False。
string str1 = ABCD1234;
string str2 = ABCD1234;
object.ReferenceEquals(str1, str2)= True;
object.ReferenceEquals(str1, ABCD1234)) = True;
令我们感到奇怪的是,当我们分别创建的引用类型两个变量——string是引用类型。照理说CLR会在托管堆(Managed Heap)中为它们分配两段内存快,他们不可能具有相同的引用才对,但是为什么object.ReferenceEquals 方法会返回True呢。而对于第二个比较——一个字符串变量和一个和他具有相同内容的字符串(ABCD1234;)直接进行比较,按照我们对CLR内存的分配的一般理解,应该是CLR首先会在托管堆中为这段字符串(ABCD1234)分配内存快,然后把相对应的引用传递给object.ReferenceEquals方法(由于分配在托管堆的这段字符串并没有被任何变量引用,所以当垃圾回收的时候会被回收掉),所以无论如何也不应该返回True。
我们先把问题留到最后,接着分析我们的Sample。上面们对字符串变量之间以及变量与字符串之间进行了比较,如果我们对一个字符串变量和一个动态创建的字符串(通过+Operator把两个字符串连接起来)进行比较,结果又会如何呢?我们来看看下面的伪代码演示:
string str3 = ABCD;
string str4 = 1234;
string str5 = ABCD + 1234;
string str6 = ABCD + str4;
string str7 = str3 + str4;
object.ReferenceEquals(str1, str5) = True
object.ReferenceEquals(str1, str6) = False
object.ReferenceEquals(str1, str7)) = False
在上面的例子中,我们用三种不同的方式创建了3个字符串变量(str5,str6,str7)——string+string;string+variable;variable+variable。然后分别和我们已经创建的、和它们具有相同字符串“值”的变量(str1)作比较。同样令我们感到奇怪的是第一个返回True,而后两个则为False。带着这些疑惑我们来看看对于string这一特殊的类型说采用的特殊的使用机制。
1. System.String虽然是一个引用类型,但是它具有其自身的特殊性。我们最容易想到的是它创建的特殊性——一般的对象在创建的时候需要通过new关键字调用对应的构造函数来实现;而创建一段string不需要这么做——我们只需要把对应的字符换赋给给对应的字符串变量就可以了。之所以存在着这种差异,是因为他们在创建过程中使用的IL指令时不同的——一般的引用对象的创建是通过newobj这样一个IL指令来实现的,而创建一个字符串变量的IL指令则是ldstr (load string)。(象C#,VB.NET这样的语言毕竟是高级语言,进行了高度的抽象,站在这样的角度分析问题往往不能够看到其实质,所以有时候我们把应该从交底层上面找突破口——比如分析IL,Metadata…);
2. 由于String是我们做到频率最高的一种类型,CLR考虑性能的提升和内存节约上,对于相同的字符串,一般不会为他们分别分配内存块,相反地,他们会共享一块内存。CLR实际上采用这个的机制来实现的:CLR内部维护着一块特殊的数据结构——我们可以把它看成是一个Hash table,这个Hash table维护者大部分创建的string(我这里没有说全部,因为有特例)。这个Hash table的Key对应的相应的string本身,而Value则是分配给这个string的内存块的引用。当CLR初始化的时候创建这个Hash table。 [1] [2] 下一页
Net Framework: 字符串的驻留(String Interning) |