String — 深入理解String

String类型是我们常用的基础数据类型之一,他是一个特殊的引用类型,在.NET里面算是少有的异类,那么特殊在什么地方呢?接下来就让深入的了解String

一、特殊之处

     (1)、创建特殊性:String对象不能使用newobj指令创建,而是ldstr指令创建,在实现机制上,CLR给了特殊的照顾优化内存

     (2)、应用上,String类型表现为值类型,但在内存中,String类型表现为引用类型,存储在托管堆里面

     (3)、两次创建内容相同的String对象可以指向相同的地址

     (4)、String类是跨应用程序域的,可以在不同的应用程序域中访问到同一String对象

     (5)、String类是密封类,不能被继承

     (6)、因为字符串驻留是进程级别的,所以可以跨AppDomin存在,即同一个字符串对象可以在不同的应用程序域被访问,突破了AppDomin的隔离,其原因还是字符串的恒定性,因为是不可变的,所以没必要在隔离

二、字符串的恒定性

    string对象和其他类型最大的区别就是它的恒定性,指的是:字符串创建后,系统会在托管堆上开辟一块连续空间,我们无法通过人为的方式去修改它,所有对string类型操作的,实际上都是返回了一个新的string,其本身不产生任何差别。

    当然这样有利也有弊:利:保证原string对象的稳定性,不会出现线程同步问题。

                                弊:创建新的字符串对象,会消耗性能和内存。所以CLR使用了一项技术来解决这个问题——字符串驻留

三、字符串驻留

      先看代码

string s1 = "小红";
string s2 = "" + ""; 
string s3 = "";
string s4 = s3 + "";   
Console.WriteLine(ReferenceEquals(s1, s2));//ReferenceEquals():是Object的一个方法,比较引用是否相等,不要用于值类型比较
Console.WriteLine(ReferenceEquals(s1, s4));

执行结果:

答案是不是有点出乎意料?

    按理说,s1和s2是两个不同的string对象,为什么引用会相同呢?看结果就算是相同的,那么s4的值也是"小红",为什么和s1又不一样了?

好的,带着我们的问题,让我们首先来了解下什么叫做字符串驻留机制:

   对于相同的字符串,CLR不会为期重新分配内存空间,而是公用同一内存地址。

那么在内部CLR是怎么实现这一功能的?

     CLR内部维护了一个hash table 来管理其创建的大部分string对象。其中key是string本身,而value为分配给对应的string的内存地址。如下如所示

 

 现在我们在来回过头看一下那两个结果,为什么是true和false

第一个结果:s1和s2,在创建s1的时候,哈希表内的key并没有"小红"这个值,所以当JIT编译的时候,会在这个哈希表的key里面添加一个"小红",然后把s1在内存中的地址方法value里面,当创建s2的时候,JIT又在这个哈希表内进行查找,然后发现key里面已经有了这个值,所以,就把key-Value对应的value赋值给了s2,所以说他们两个的引用是相同的,那么为什么第二个结果是false呢?

第二个原因:虽然s4的结果最后也是"小红",但是在创建的过程中,发生了变化,s4是动态生成的字符串,这样的字符串是不会被添加到哈希表中进行维护的,所以返回为false。

这样,我们就能很容易的解释上述代码的原因了。

补充:对于动态生成的字符串,没有添加到CLR的哈希表中导致字符串驻留失效的,我们可以通过两个静态的string方法,手工启用字符串驻留机制

string s1="小红";
string s3="";
//s4 = string.Intern(s3+"红");  //true
s4 = string.IsInterned(s3+"");  //true
Console.WriteLine(ReferenceEquals(s1, s4));

public static String Intern(String str);

     //如果暂存了 str,则返回系统对其的引用;否则返回对值为 str 的字符串的新引用。

public static String IsInterned(String str);

    //此方法在暂存池中查找 str。 如果已经将 str 放入暂存池中,则返回对此实例的引用;否则返回 null。

两者的区别:

同样,如果我们不想使用字符串驻留机制,可以使用string.copy()来解除这一限定

string s1 = "小红";
string s2 = string.Copy(s1);
Console.WriteLine(ReferenceEquals(s1, s2));  //false

public static String Copy(String str);

 

特别指出:字符串驻留进程级别的,可以跨应用程序域(AppDomain)而存在,也就是不同的类里面相同的字符串值同样是同一个引用。垃圾回收不能释放哈希表中的引用字符串对象,只有进程结束这些对象才会被释放

  *根据《你必须知道的.NET》整理

posted @ 2017-02-23 15:08  随风飘过  阅读(504)  评论(2编辑  收藏  举报