一、在.NET中string是一种特殊的引用类型,它一旦被赋值在堆上的地址即不可改变,之后对其进行的字符串相加等操作之后的结果都指向另外一个堆地址,而非原来的字符串地址。现在我们看以下一段C#代码以观察string在实际编码过程中的使用。
- class Program
- {
- static void Main(string[] args)
- {
- //打印One Word
- string str1 = "One";
- str1 += " Word";
- Console.WriteLine(str1);
- string str2 = "One"+" Word";
- Console.WriteLine(str2);
- //打印One Word43
- int i = 43;
- Console.WriteLine(str1+i);
- Console.WriteLine(str1 + i.ToString());
- }
- }
二、上面的C#生成的CIL代码如下:
- .method private hidebysig static void Main(string[] args) cil managed
- {
- //初始化
- .entrypoint
- // 代码大小 80 (0x50)
- .maxstack 2
- .locals init ([0] string str1,
- [1] string str2,
- [2] int32 i)
- //两个字符串分为两个步骤相加
- IL_0000: nop
- IL_0001: ldstr "One"
- IL_0006: stloc.0
- IL_0007: ldloc.0
- IL_0008: ldstr " Word"
- IL_000d: call string [mscorlib]System.String::Concat(string,
- string)
- IL_0012: stloc.0
- IL_0013: ldloc.0
- IL_0014: call void [mscorlib]System.Console::WriteLine(string)
- //两个字符串在同一行里面进行相加 被翻译为CIL时就已经连接到一起
- IL_0019: nop
- IL_001a: ldstr "One Word"
- IL_001f: stloc.1
- IL_0020: ldloc.1
- IL_0021: call void [mscorlib]System.Console::WriteLine(string)
- //将Int32字符装箱再和String合并打印
- IL_0026: nop
- IL_0027: ldc.i4.s 43
- IL_0029: stloc.2
- IL_002a: ldloc.0
- IL_002b: ldloc.2
- IL_002c: box [mscorlib]System.Int32
- IL_0031: call string [mscorlib]System.String::Concat(object,
- object)
- IL_0036: call void [mscorlib]System.Console::WriteLine(string)
- //直接调用数字的ToString()方法
- IL_003b: nop
- IL_003c: ldloc.0
- IL_003d: ldloca.s i
- IL_003f: call instance string [mscorlib]System.Int32::ToString()
- IL_0044: call string [mscorlib]System.String::Concat(string,
- string)
- IL_0049: call void [mscorlib]System.Console::WriteLine(string)
- IL_004e: nop
- IL_004f: ret
- } // end of method Program::Main
三、首先我们看两种字符串的构造方式的不同而引起的效能变化。
第1种
- //打印One Word
- string str1 = "One";
- str1 += " Word";
- Console.WriteLine(str1);
- //第一种、两个字符串分为两个步骤相加
- IL_0000: nop
- IL_0001: ldstr "One" //将One字符串存到堆上并且将其引用压栈
- IL_0006: stloc.0 //从栈顶部的One字符串引用地址弹出到第一个参数0
- IL_0007: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上
- IL_0008: ldstr " Word" //将Word字符串存到堆上并且将其引用返回到计算机栈上
- //调用系统函数将上面两个字符串相加其结果存到栈顶
- IL_000d: call string [mscorlib]System.String::Concat(string,
- string)
- IL_0012: stloc.0 //从栈顶部的One字符串引用地址弹出到第一个参数0
- IL_0013: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上
- //调用系统参数打印One Word
- IL_0014: call void [mscorlib]System.Console::WriteLine(string)
第2种
- string str2 = "One"+" Word";
- Console.WriteLine(str2);
- //两个字符串在同一行里面进行相加 被翻译为CIL时就已经连接到一起
- IL_0019: nop
- IL_001a: ldstr "One Word" //直接已经构造成One Word
- IL_001f: stloc.1 //从栈顶部的One Word字符串引用地址弹出到第一个参数1
- IL_0020: ldloc.1 //将索引 1 处的局部变量加载到计算堆栈上
- //调用系统参数打印One Word
- IL_0021: call void [mscorlib]System.Console::WriteLine(string)
结论:通过上面两种方式构造方式的CIL我们可以很清晰的看出第二种方式的效率要高于第一种的字符串构造方式。所以我们在实际的编码过程中可以考虑尽量使用第二种编码方式。
四、大家都知道装箱操作会在堆上寻找一个控件来存储值类型的值。会耗费大量的时间。所以下面我们来看两个实例代码
第1种
- int i = 43;
- Console.WriteLine(str1+i);
- //将Int32字符装箱再和String合并打印
- IL_0026: nop
- IL_0027: ldc.i4.s 43
- IL_0029: stloc.2
- IL_002a: ldloc.0
- IL_002b: ldloc.2
- IL_002c: box [mscorlib]System.Int32 //在这里有一个装箱的操作
- IL_0031: call string [mscorlib]System.String::Concat(object,
- object)
- IL_0036: call void [mscorlib]System.Console::WriteLine(string)
第2种
- int i = 43;
- Console.WriteLine(str1 + i.ToString());
- //直接调用数字的ToString()方法
- IL_003b: nop
- IL_003c: ldloc.0
- IL_003d: ldloca.s i
- //这里没有装箱的操作,仅仅调用了重载的ToString()方法
- IL_003f: call instance string [mscorlib]System.Int32::ToString()
- IL_0044: call string [mscorlib]System.String::Concat(string,
- string)
- IL_0049: call void [mscorlib]System.Console::WriteLine(string)
- IL_004e: nop
结论:在这里我们分析第一种情况是对Int32值进行了box装箱操作,然后调用系统函数和第一个 string相加才得到结果的。而第二种情况是直接对Int32调用其重载函数ToString()得到Int32值的字符再与第一个字符相加。在这里我 们避免了装箱分配堆地址查找空余堆地址等麻烦,所以如果再一个循环语句中,第二种情况肯定效率要高很多。
本文转自程兴亮 51CTO博客,原文链接:http://blog.51cto.com/chengxingliang/826595