在.NET程序中正确使用String类型

转载 2007年09月17日 22:32:00

在实际程序中,String类型用得非常广泛,然而,由于.NET对String类型变量的独特管理方式,使用不当,会严重影响程序的性能。我们分几个方面来谈这个问题:

1 了解String数据的内存分配方式
编写一个控制台应用程序,输入以下测试代码:

     class Program
     {
         static void Main(string[] args)
         {
             String s = "a";
             s = "abcd";
         }
     }

使用.NET Framework 2.0 SDK提供的ildasm.exe工具查看生成的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

简要解释一下上述MSIL指令代码:
第06句给局部变量s分配一个索引号(索引号从0开始,如函数中有多个局部变量,其索引号按在函数中出现的顺序加一)。
在编译时编译器会将代码中的两个字串“a”和“abcd”写入到程序集的元数据(metadata)中,此时,这两个字串被称为“字串字面量(string literal)”。
第08句使用ldstr指令为字串对象“a”分配内存,并将此对象引用压入到线程堆栈中。
第09句使用stloc指令从线程堆栈顶弹出先前压入的对象引用,将其传给局部变量s(其索引号为0)。
同样的过程对“abcd”重复进行一次,所以这两句简单的代码

             String s = "a";
             s = "abcd";

将会导致CLR使用ldstr指令分配两次内存。
根据上述分析,读者一定明白了String变量的内容是只读的,给其赋不同的值将会导致内存的重新分配。因此,为提高程序性能,编程时应尽量减少内存的分配操作。
下面对代码中常见的字串用法进行分析,从中读者可以知道如何避免严重影响程序性能的字串操作。

2 尽量少使用字串加法运算符


请看以下两段代码:

   (1)           String s1 = "ab";
                      s1+="cd";
   (2)           String s1="ab"+"cd";

这两段代码运行结果一样,但速度一样快吗?
请看第(1)段代码生成的MSIL指令:

   .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

可以很清楚地看到,第(1)段代码将导致String类的Concat()方法被调用(实现字串加法运算)。对于第(2)段代码,由于C#编译器聪明地在编译时直接将两个字串合并为一个字串字面量,所以程序运行时CLR只调用一次ldstr指令就完成了所有工作,其执行速度谁快就不言而喻了!


3 避免使用加法运算符连接不同类型的数据


请看以下代码:

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

可以看到,虽然多了一次方法调用(Console.Write)方法,但却避免了复杂的装箱操作,也避免了调用String.Concat()方法对内存的频繁分配操作,性能更好。


4.在循环中使用StringBuilder代替String实现字串连接


在某些场合需要动态地将多个子串连接成一个大字串,比如许多复杂的SQL命令都是通过循环语句生成的。这时,应避免使用String类的加法运算符,举个简单的实例:

             String str ="";
             for (int i = 1; i <= 10; i++)
             {
                 str += i;
                 if(i<10)
                     str += "+";
             }

上述代码将生成一个字串:1+2+…+10。
有了前面的知识,读者一定知道这将导致进行10次装箱操作,19次字串内存分配操作(由String.Concat()方法引发),由于生成的MSIL指令太长,此处不再列出,请读者自行用ildasm.exe工具查看上述代码生成的MSIL指令。
改为以下代码,程序性能会好很多:

            //预先分配1K的内存空间
             StringBuilder sb = new StringBuilder(1024);
             for (int i = 1; i <= 10; i++)
             {
                 sb.Append(i);
                 if(i<10)
                     sb.Append("+");
             }
             String result = sb.ToString();

通过使用ildasm.exe工具查看生成的MSIL代码,发现虽然上述代码生成的MSIL指令比前面多了7条,但却避免了耗时的装箱操作,而且内存分配的次数也少了很多。当循环的次数很大时,两段代码的运行性能差异很大。
 
 

在.NET程序中正确使用String类型

  • 2010年03月05日 10:16
  • 44KB
  • 下载

VB.NET String数据类型操作技巧

如果一个变量总是存储诸如“我是中国人”之类的字符串而不包含3.1415926这样的数值,则可将其声明为String类型。   在VB.NET中,一个字符串可包含大约2亿(2的31次方)个Unicod...

vb.net 教程 1-2 数据类型:String

String:字符串型讲解

vb.net的String数据类型

如果一个变量总是存储诸如“我是中国人”之类的字符串而不包含3.1415926这样的数值,则可将其声明为String类型。 在vb.net中,一个字符串可包含大约2亿(2的31次方)个Unicode字...
  • ly_wolf
  • ly_wolf
  • 2011年05月27日 16:30
  • 420

略谈ASP.NET中C#的string引用类型

前面我有谈到关于javascript的String对象,这里谈一谈C#的string类型,希望大家能够将两者区分开来,掌握两种语言中string的不同用法。 首先C#中定义字符串的方法: stri...

.NET源码中String类型的实现

String类型是.net中极其重要且常用的基础数据类型,其内部数据结构实现如下: public sealed class String : IComparable, ICloneable, ICo...
  • cuit
  • cuit
  • 2014年06月30日 23:30
  • 766

QThread 正确使用方法 - tangaowen的专栏 - 博客频道 - CSDN.NET

目录(?)[+] A short history QThreadrun is the thread entry point Usage 1-0 Usage 1-1 Usage 1-2 Wrong Us...

ASP.NET中如何正确使用Session

Asp.Net中的Session要比Asp中的Session灵活和强大很多,同时也复杂很多;看到有一些Asp.Net开发人员报怨说Session不稳定,莫名其妙的丢失,其实这正是Asp.Net改进的地...

转载:Asp.Net之Html控件与Web控件的区别及如何正确使用它们.

讨论内容:  Asp.Net之Html控件与Web控件的区别及如何正确使用它们. 1  DOT.NET中常用的WEB表示层控件分为[HTML控件]和[WEB服务端控件],位于System.W...

ASP.NET中的Session怎么正确使用

转自:http://www.cr173.com/html/24780_1.html     Session对象用于存储从一个用户开始访问某个特定的aspx的页面起,到用户离开为止,特定的用户会话所需...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:在.NET程序中正确使用String类型
举报原因:
原因补充:

(最多只允许输入30个字)