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》整理