C#字符串操作
1.导读
本文主要讨论C#字符串操作时如何避免性能损耗。在此之前需要熟悉以下几个概念:
1)装箱和拆箱
装箱是将值类型转换为 object 类型或由此值类型实现的任何接口类型的过程. 当 CLR 对值类型进行装箱时,会将该值包装到 System.Object内部,再将后者存储在托管堆上。取消装箱将从对象中提取值类型。装箱是隐式的;取消装箱是显式的。
关于装箱拆箱详细内容:http://msdn.microsoft.com/zhcn/library/yz2be5wk.aspx
2)IL代码
IL是.NET框架中中间语言(Intermediate Language)的缩写。使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执行的机器代码,而是一种中间语言IL(Intermediate Language)的代码。IL语言的优点是只要把.NET框架某种语言(C#/VB)编译成IL代码,就实现.NET框架中语言之间的交互操作。
IL语言更多相信内容:http://www.cnblogs.com/xiaoxiangfeizi/archive/2011/08/08/2130768.html
3)托管与非托管
托管的意思,你不用直接操作内存,你需要的时候跟CLR说。CLR替你申请,然后给你用,你用完可以告诉CLR,CLR帮你释放,如果你忙,忘记告诉CLR了,CLR也会在定期去帮你释放的。这就是托管,你打交道的不是直接的内存,而是.net CLR。
非托管的意思就是你要自己负责管理内存,这里所说的内存管理。实际上只是堆上的内存管理,栈内存和以前的一样,函数退出则释放,heap上的内存,非托管内存需要自己分配,调用构造函数(c需要,c++里用new替代这部操作了),使用完毕后,需要自己释放这个内存,如果你不小心,把指向内存的指针弄丢了,就造成内存泄露了,这个内存泄露在你程序退出之前是无法弥补的,所以要小心。
4)FCL与CLR
FCL:Framework Class Library,即Framework类库。
CLR(公共语言运行库,Common Language Runtime)和Java虚拟机一样也是一个运行时环境,是一个可由多种编程语言使用的运行环境。CLR的核心功能包括:内存管理、程序集加载、安全性、异常处理和线程同步,可由面向CLR的所有语言使用。并保证应用和底层操作系统之间必要的分离。CLR是.NET Framework的主要执行引擎。
更多关于CLR的内容请阅读《C# VIA CLR》
5)String和 StringBuilder
String在进行拼接操作时需要新开辟内存区域,为了弥补这个缺陷,微软提供了StringBuilder这个类型,StringBuilder开始以非托管形式分配内存,只有字符串长度大于默认初始长度时才会分配新的内存区域。
2.C#操作字符串的性能损耗
C#操作字符串的性能损耗通常来自于装箱,下面这两行代码中,第一行代码由于要进行装箱操作,性能比第二行低。
1 String str1 = “str1”+ 9;
2 String str2 = “str2”+9.ToString();
有人会质疑,ToString方法会不会存在装箱行为呢,我们来看看ToString方法的原型如下:
Public override StringToString()
{
Retuen Number.Format32(m_value,null,NumberFormatInfo.CurrentInfo);
}
可能有人继续追问,是不是Number.Format32方法中存在装箱行为呢?事实上,Number.Format32是一个非托管方法:
[MethodImpl(MethodImplOptions.InternalCall),SecurityCritical]
Public static extern stringFormatInt32(int value,string format,NumberFormatInfo info);
它是通过直接访问内存来完成int到string的转换,效率要比装箱高得多,所以,在使用其他值引用类型到字符串的转换并完成拼接时,应当避免使用操作符“+”来完成,而应该使用值引用类型的ToString方法。
讨论到这里,有人会疑惑,在刚才讨论的例子中,FCL提供的方法中没有装箱行为,那是不是在其他任何情况下,FCL提供的方法中会不会存在装箱行为呢?答案是:可能会存在!不过,指导原则是:
在自己编写的代码中,应当尽可能地避免编写不必要的装箱代码;
这里补充说明下,装箱之所以会带来性能损耗,主要是由于装箱需要以下三个步骤:
1. 为值类型在托管堆中分配内存。除了值类型本身的内存外,还要加上类型对象的指针和同步块索引所占用的内存;
2. 将值类型的值复制到新分配的堆内存中;
3. 返回已经成为引用类型的对象的地址;
C#操作字符串性能损耗来自于额外的内存空间的分配。String类型对于CLR来说,一旦赋值就不可以改变,在运行时调用调用System.String的任何方法或进行任何计算(+,=)在内存中都会创建一个新的字符串对象,这意味着要为新的字符串对象分配新的内存空间。通常我们
采用StringBuilder类型来代替String类型。