C# 之 string 解惑
string是一种很特殊的数据类型,既是基元类型又是引用类型,在编译以及运行时,.Net都对它做了一些优化工作,正式这些优化工作有时会迷惑编程人员,让人怀疑它是否真的是引用类型?
疑惑一:字符串恒定
1 string a = "string 01";
2 string b = a;
3 a = "string 02";
4 Console.WriteLine("a : {0}", a);
5 Console.WriteLine("b : {0}", b);
6 Console.WriteLine("a reference equal b is {0}",ReferenceEquals(a, b));
b值恒定,没有随a值得改变而改变,a和b不指向同一个内存地址,Why?
疑惑二:字符串驻留
1 string a = "string 03";
2 string b = "string 03";
3 Console.WriteLine("a reference equal b is {0}",ReferenceEquals(a, b));
新建2个引用类型,为什么会指向同一个内存地址?
疑惑三:string = 与new stirng()的区别
1 string a = "x";
2 string b = new string('x',1);
3 Console.WriteLine("a reference equal b is {0}",ReferenceEquals(a, b));
此时为什么a和b又不指向同一个内存地址?
要弄清楚上述你问,我们有必要先了解一个托管程序是如何运行的。
当运行一个托管程序,CLR会为此创建一个Default AppDomain,但实际上Windows为程序做的事情远不止这些。之所以说一个Application是在一个托管的环境下执行的,是指CLR对Application进行托管。所以CLR的加载是必须的。而.NET Framework是建立在Windows平台之上的,Windows可以看做是对计算机硬件的封装,而.NET Framework则可以看成是对Windows的封装,通过.NET Framework API封装了对传统Win32的封装。正是因为Windows是.NET Framework的基础架构,所以.NET Framework只能是利用Windows所能理解的方式进行构建。对于Windows来说,所有能被加载执行的都是一个PE文件(Portable Executable file),比如exe和dll。CLR也一样,他实际上是一个COM Server的形式实现在一个叫做MSCorWks.Dll中,该Dll存在于.NET Framework对应的目录中。当程序开始运行的时候,有一个称为SystemDomain的AppDomain被创建,SystemDomain加载MSCorEE.Dll。通过定义在该DLL中的一个名为CorBindToRuntimeEx的函数加载对应版本的CLR,并返回一个非托管的ICLRRuntimeHost interface。SystemDomain是整个Process的枢纽,它负责创建、初始化、卸载SharedDomain和DefaultDomain。
我们知道AppDomain是一个Assembly的托管容器,Assembly在一般情况下是基于某个单独的AppDomain的,不能与另一个AppDomain共享的。但是有些公用性很强的Assembly,比如我们经常使用的一些基元类型object, int,Array,ValueType等,却希望它被一个AppDomain加载之后,能够被其他的AppDomain共享,这样就可以省去很多内存空间和Assembly加载带来的性能损失。这些Assembly就是被加载到SharedDomain中,我们常用的MScorLib.dll就是被以这样的方式被加载的SharedDomain中的。Default Domain就是为具体的Application创建的AppDomain,它一般以可执行文件名命名。DefaultDomain中可以通过AppDomain.CreateAppDomain创建另一个AppDomain。所以当我们运行一个托管的Application的时候,实际上创建了3个不同AppDomain:SystemDomain,ShatedDomain和DefaultDomain,而SystemDomain和ShatedDomain基于整个进程的,能够被DefaultDomain以及被它创建AppDomain共享的。
有了上面的基础,我们就不难理解String的驻留机制的。String的驻留机制实际上是在SystemDomain中进行的。当CLR被加载之后,会在SystemDomain对应的managed heap中创建一个Hash table的数据结构,我们可以称这个Hashtable为Interning table,因为它是被用来保存被驻留的string的,Interning table的Key为string本身,Value为string对象的地址。当我们的托管程序需要一个string的时候,CLR首先在这个Hashtable根据这个string的hash code试着在Interning table中找对应的Item。如果成功找到,则直接把对应的引用返回,否则就在SystemDomain对应的managed heap中创建该string,并加入到Interning table中,并把引用返回。所以我们说字符串的驻留是基于整个进程的,是可以跨AppDomain共享的。
答疑
- String的恒定性:String一经创建,在托管环境中它所对应的字符序列就无法更改,因此我们在做字符串拼接的时候要用Stringbuilder,否则会产生大量驻留的字符串。
- String的驻留:CLR对String的创建实行驻留机制,CLR只会维护具有不同字符序列的String。即在程序中使用到的具有完全相同的字符序列的String均是对应着同一个string对象,是对同一个段内存的引用,而且String的驻留机制不仅仅是基于某个单独基于AppDomain的,而是针对整个进程的。
- string = 与new stirng()的区别:“string = ”加载字符串的时间不同:第一行的“x”是一个常量,在编译期就已经被放在一个叫做常量池的地方了,常量池通常装载一些在编译期被确定下来的数据;而“new stirng()”是运行时CLR在堆中生成的值为“x”的字符串对象,所以后者没有字符串驻留。
作者:RobertFang 出处:Http://fangrobert.cnblogs.com/ 欢迎转载,请转载的文章页面明显位置给出原文链接。 |