【IT168技术文档】
在实际程序中,String类型用得非常广泛,然而,由于.NET对String类型变量的独特管理方式,使用不当,会严重影响程序的性能。我们分几个方面来谈这个问题:
了解String数据的内存分配方式
编写一个控制台应用程序,输入以下测试代码:
使用.NET Framework 2.0 SDK提供的ildasm.exe工具查看生成的MSIL指令:class Program { static void Main(string[] args) { String s = "a"; s = "abcd"; } }
简要解释一下上述MSIL指令代码:01 .method private hidebysig static void Main(string[] args) cil managed 02 { 03 .entrypoint 04 // 代码大小 14 (0xe) 05 .maxstack 1 06 .locals init ([0] string s) 07 IL_0000: nop 08 IL_0001: ldstr "a" 09 IL_0006: stloc.0 10 IL_0007: ldstr "abcd" 11 IL_000c: stloc.0 12 IL_000d: ret 13 } // end of method Program::Main
第06句给局部变量s分配一个索引号(索引号从0开始,如函数中有多个局部变量,其索引号按在函数中出现的顺序加一)。
在编译时编译器会将代码中的两个字串“a”和“abcd”写入到程序集的元数据(metadata)中,此时,这两个字串被称为“字串字面量(string literal)”。
第08句使用ldstr指令为字串对象“a”分配内存,并将此对象引用压入到线程堆栈中。
第09句使用stloc指令从线程堆栈顶弹出先前压入的对象引用,将其传给局部变量s(其索引号为0)。
同样的过程对“abcd”重复进行一次,所以这两句简单的代码
将会导致CLR使用ldstr指令分配两次内存。string s = "a"; s = "abcd";
根据上述分析,读者一定明白了String变量的内容是只读的,给其赋不同的值将会导致内存的重新分配。因此,为提高程序性能,编程时应尽量减少内存的分配操作。
下面对代码中常见的字串用法进行分析,从中读者可以知道如何避免严重影响程序性能的字串操作。
尽量少使用字串加法运算符
请看以下两段代码:
这两段代码运行结果一样,但速度一样快吗?(1) string s1 = "ab"; s1+="cd"; (2) string s1="ab"+"cd";
请看第(1)段代码生成的MSIL指令:
可以很清楚地看到,第(1)段代码将导致String类的Concat()方法被调用(实现字串加法运算)。对于第(2)段代码,由于C#编译器聪明地在编译时直接将两个字串合并为一个字串字面量,所以程序运行时CLR只调用一次ldstr指令就完成了所有工作,其执行速度谁快就不言而喻了!.locals init ([0] string s1) IL_0000: nop IL_0001: ldstr "ab" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldstr "cd" IL_000d: call string [mscorlib]System.String::Concat(string, string) IL_0012: stloc.0 IL_0013: ret 再看第(2)段代码生成的指令: .locals init ([0] string s1) IL_0000: nop IL_0001: ldstr "abcd" IL_0006: stloc.0 IL_0007: ret
避免使用加法运算符连接不同类型的数据
请看以下代码:
String str = "100+100=" + 200; Console.Writeline(str); 生成的MSIL指令为: .maxstack 2 .locals init ([0] string str) IL_0000: nop IL_0001: ldstr "100+100=" IL_0006: ldc.i4 0xc8 IL_000b: box [mscorlib]System.Int32 IL_0010: call string [mscorlib]System.String::Concat(object, object) IL_0015: stloc.0 IL_0016: ldloc.0 IL_0017: call void [mscorlib]System.Console::WriteLine(string) IL_001c: nop IL_001d: ret 可以清晰地看到,这两句C#代码不仅导致了String类的Concat()方法被调用(IL_0010),而且还引发了装箱操作(IL_000b)! Concat()方法会导致CLR为新字串分配内存空间,而装箱操作不仅要分配内存,还需要创建一个匿名对象,对象创建之后还必须有一个数据复制的过程,代价不菲! 改为以下代码: String str = "100+100="; Console.Write(str); Console.WriteLine(200); 生成的MSIL指令为: .maxstack 1 .locals init ([0] string str) IL_0000: nop IL_0001: ldstr "100+100=" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: call void [mscorlib]System.Console::Write(string) IL_000d: nop IL_000e: ldc.i4 0xc8 IL_0013: call void [mscorlib]System.Console::WriteLine(int32) IL_0018: nop IL_0019: ret