您的位置:首页 > 编程语言 > C#

全面剖析C#之String对象

2011-07-15 08:02 141 查看
相信有很多开发人员都有这样的面试经历:面试官就某个问题对你追着问,不仅问你是什么,还要问你为什么以及它的内部机制,直至他认为你把问题阐述的非常透彻才肯罢手,这就要求我们的开发人员对这些问题要做到深刻的理解。正是基于此,才有了本篇随笔的产生,在这篇文章里我将着重阐述我对String对象的理解,例如String的类型,它的内存分配模型以及它适合在什么情况下使用等等。

String VS string

其实二者的作用是一样的,之所以说它们是一样的,是因为在编译的时候,CLR在其内部使用了using string = System.String这样一个表达式,换句话说string就代表了String,或者说string是String的一个别名,只不过需要注意的是前者是C#的一个对象,而后者是C#的一个关键字,C#中类似的关键字还有例如int, bool, float等等。

String之类型

String是一个引用类型,虽然其行为看起来像是一个值类型,下面将通过一个Sample来说明,为此我们先建一个Console应用程序如下:

代码

1 .method private hidebysig static void Main(string[] args) cil managed
2 {
3 .entrypoint
4 // Code size 62 (0x3e)
5 .maxstack 2
6 .locals init ([0] string str1,
7 [1] string str2)--初始化所有变量
8 IL_0000: nop
9 IL_0001: ldstr "This is a string"--推送新对象引用至栈中
IL_0006: stloc.0 --取栈顶值并赋予内存变量str1
IL_0007: ldloc.0 --取内存变量str1值并入栈
IL_0008: stloc.1 --取栈顶值并赋予内存变量str2
IL_0009: ldloc.0 --取内存变量str1值并入栈
IL_000a: ldloc.1 --取内存变量str2值并入栈
IL_000b: call bool [mscorlib]System.String::op_Equality(string,
string)--调用方法比较str1和str2
IL_0010: call void [mscorlib]System.Console::WriteLine(bool)--输出比较结果
IL_0015: nop
IL_0016: ldstr "This is another string"--推送新对象引用至栈中
IL_001b: stloc.0 --取栈顶值并赋予内存变量str1
IL_001c: ldloc.0 --取内存变量str1值并入栈
IL_001d: call void [mscorlib]System.Console::WriteLine(string)--输出str1
IL_0022: nop
IL_0023: ldloc.1 --取内存变量str2值并入栈
IL_0024: call void [mscorlib]System.Console::WriteLine(string)--输出str2
IL_0029: nop
IL_002a: ldloc.0 --取内存变量str1值并入栈
IL_002b: ldloc.1 --取内存变量str2值并入栈
IL_002c: call bool [mscorlib]System.String::op_Equality(string,
string)--调用方法比较str1和str2
IL_0031: call void [mscorlib]System.Console::WriteLine(bool)--输出比较结果
IL_0036: nop
IL_0037: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_003c: pop
IL_003d: ret
} // end of method Program::Main

上面的IL代码说明几个问题,下面将一一加以解释。

第一,为什么改变str1的值不会影响str2的值:

给str1第一次初始化赋值(string str1 = "This is a string";)的IL代码是IL_0001: ldstr "This is a string"--推送新对象引用至栈中,

而后来改变str1值(str1 = "This is another string";)的IL代码是IL_0016: ldstr "This is another string"--推送新对象引用至栈中,

显然,原来每次赋值或者说改变str1的值,都会导致新对象(String)的创建,所以说无论怎么改变str1的值都不会影响str2,因为你改变str1相当于创建了一个全新的String对象,和str2一点关系都没有,另外,这也解释和说明了String是不可变的(所以说我们想‘改变’字符串值的美好愿望是徒劳的),进一步地,也告诉我们为什么不能频繁地改变String的值,因为这将导致String对象的频繁创建与与销毁(GC),这对性能是一个极大的损耗。

第二,为什么语句string str2 = str1;不会导致新对象的创建:

IL_0008: stloc.1 --取栈顶值并赋予内存变量str2

仅仅是把str1取出来然后赋给str2,这又是为什么呢?原来CLR为了提高String的使用效率,对其使用了字符串驻留/拘留技术,即在程序编译的时候,CLR就会收集所有用到的字符串变量的值并把其放入元数据中,然后在内存中创建了一张用于维护这些字符串的散列表,键值分别为字符串的值和对象在托管堆中的引用,这样做有两个好处,1)下次如果需要创建新的字符串,CLR会先检查这个字符串的值在表中是否存在,如果存在,就不会创建新的字符串对像,而只是使字符串引用到对应的键值对;如果不存在才会创建,这样做极大地提高了字符串的使用效率;2)由于具有相同值的字符串在会在表中保存一次,这就保证了在使用时的一致性。

User Strings
-------------------------------------------------------
70000001 : (16) L"This is a string"
70000023 : (22) L"This is another string"
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: