逐一回答AnyTao“字符串驻留“一文中的问题,如果有不准确之处,敬请指正。

AnyTao“字符串驻留”一文链接地址:http://www.cnblogs.com/anytao/archive/2008/08/27/must_net_22.html

昨天,看到AnyTao的这篇文章时是第一次听说“字符串驻留”,呵呵,真惭愧。

AnyTao没有在文章中给出8个测试的结果原因,下面是我自己的分析。

 

1.

   static void Main() 

        {

            string s1 = "abc";

            Console.WriteLine(string.IsInterned(s1) ?? "null");

        }

结果输出为:abc

 

分析:首先“??”符号也是在AnyTao文章中看到的,也是我第一次知道,该符号为二元运算符,如果“??”的左边不为空则返回符号左边实际应返回的数据(如果左边不是计算得到的那就直接返回左边的内容),否则返回“??”符号右边的内容。

其次就是“字符串驻留”。当CLR初始化时,它会创建一个内部的散列表,其中的键为字符串,值为指向托管堆中字符串对象的引用。刚开始,此散列表为空,当JIT编译器编译方法时,它会在散列表中查找每一个文本常量字符串,如果查找到则返回散列表中该常量字符串对应的(也即托管堆中对象的引用);如果找不到,则在托管堆中构造一个新的string对象指向该常量字符串,然后将该常量字符串和指向该对象的引用添加到散列表里面。

而string.IsInterned()方法判断指定参数是否在散列表里面,该方法接收一个string作为参数,并会在CLR内部的散列表中查找它。如果CLR内部的散列表中含有该字符串,IsInterned将返回散列表中保存的字符串的对象引用;否则,将返回null,而且不会静该字符串添加到散列表中。

所以,当编译执行到string s1 = "abc";时,CLR首先会在托管堆构造一个新的string对象(指向“abc”),然后将文本常量字符串“abc”和指向s1对象的引用添加到散列表里面。然后string.IsInterned(s1)判断s1是在散列表里面的,所以会输出“abc”。

 

记住,CLR的内部散列表里面保存的是源代码的文本常量字符串。CLR的内部散列表是在进程中的所有AppDOMAIN(应用程序域)不再引用这些字符串对象时,垃圾收集器才对其进行释放。

 

2.

static void Main() 
        { 
            string s1 = "ab"; 
            s1 += "c"; 
            Console.WriteLine(string.IsInterned(s1) ?? "null"); 
        }

输出结果:null

分析:

 

如图,通过查看ILDASM发现,执行ldstr的只是两个常量字符串"ab"和"c",所以字符串”abc”并不在散列堆里面。所以输出结果为”null”。

 

3.

static void Main() 
        { 
            string s1 = "abc"; 
            string s2 = "ab"; 
            s2 += "c"; 
 
            string s3 = "ab"; 
            
            Console.WriteLine(string.IsInterned(s1) ?? "null"); 
            Console.WriteLine(string.IsInterned(s2) ?? "null"); 
 
            Console.WriteLine(string.IsInterned(s3) ?? "null"); 
        }

输出结果:

abc

abc

ab

通过1、2的分析,我们知道编译结束时散列表里面的文本常量字符串有:”abc”、”ab”、”c”。

string.IsInterned(s1)和string.IsInterned(s3)就不用说了,答案显而易见。说一下string.IsInterned(s2),s2的结果是”abc”,而”abc”在散列表里面是存在的,所以string.IsInterned(s2)结果不为空,将返回会返回字符串”abc”的引用

,我想应该是s1的地址吧。所以Console.WriteLine(string.IsInterned(s2) ?? "null"); 输出”abc”。
 
针对问题3,我增加了一个对比示例,如下:

static void Main()

        {

            string s2 = "ab";

            s2 += "c";

            string s3 = "ab";

            Console.WriteLine(string.IsInterned(s2) ?? "null");

            Console.WriteLine(string.IsInterned(s3) ?? "null"); 
}
输出结果:
null
ab
这样的输出结果与AnyTao问题3的结果区别的原因应该比较清楚了吧?O(∩_∩)O~
 
 
4.
static void Main() 
        { 
            string s1 = "abc"; 
            string s2 = "ab"; 
            string s3 = s2 + "c"; 
            
            Console.WriteLine(string.IsInterned(s3) ?? "null"); 
        }
输出结果:abc
这个由前面的分析可以看出结果,在此不再分析。
 
5.
static void Main() 
        { 
            string s2 = "ab"; 
            s2 += "c"; 
            
            Console.WriteLine(string.IsInterned(s2) ?? "null"); 
            
            string s1 = "abc"; 
        }
输出结果:
abc
分析:不管string s1 = "abc";是放在Console.WriteLine(string.IsInterned(s2) ?? "null"); 前面还是后面,在编译的时候,文本常量字符串"abc"都被放到了CLR的散列表里面,所以Console.WriteLine(string.IsInterned(s2) ?? "null");输出”abc”是无容置疑的~。
 
6.

static void Main()

        {

tring s2 = "ab";

            s2 += "c";

            Console.WriteLine(string.IsInterned(s2) ?? "null"); //null

            string s1 = GetStr();

}

 

        private static string GetStr() { return "abc"; }
 
输出结果:null
分析:虽然函数GetStr()返回的是”abc”,但是散列表里面只存在”ab”和”c”。
 
7.
public const string s1 = "abc"; 
        
        static void Main() 
        { 
            string s2 = "ab"; 
            s2 += "c"; 
            
            Console.WriteLine(string.IsInterned(s2) ?? "null"); 
        }
输出结果:null
分析:
先看一下ILDASM的结果吧:
 
由此看出散列表里面存在的是”ab”和”c”两个文本常量。所以输出结果是”null”。
 
8.
public static string s1 = "abc"; 
        
        static void Main() 
        { 
            string s2 = "ab"; 
            s2 += "c"; 
            
            Console.WriteLine(string.IsInterned(s2) ?? "null"); 
        }
输出结果:abc
分析:为什么7输出null,而8输出abc呢?什么原因?呵呵,一个是常量一个是静态变量。对比7的反编译我们看一下8的,如图:
 
我们看到,8的反编译结果里面多了一个函数.cctor,它是类型构造器,在类中存在编译时的静态成员初始化时,类会自动添加一个私有的构造函数,反编译后IL里面体现为.cctor,而.ctor是构造函数。我们由.cctor得内容可以看得出”abc”文本常量字符串放入了散列表里面,所以输出结果为”abc”。
 
 
补充:
字符串驻留机制基于String对象的恒定不变性,有助于节省内存。对于动态创建的字符串(比如:string+variable;variable+variable),驻留机制便不起作用,因为驻留机制仅是将源代码中的文本常量字符串放入散列表里面的。而诸如string+variable;variable+variable这些是调用了concact()函数,这可以由反编译代码看出。
 
感谢:
感谢AnyTao,是你的分享,让我提高了自己。
posted @ 2010-05-06 20:16  Edenia  Views(1692)  Comments(23Edit  收藏  举报