• 写出高性能C#程序你需要注意的字符串处理细节
  • 崽崽 发表于 2016/3/9 20:04:00 | 分类标签: 性能优化 .NET 内存分配
  • 字符串应该是所有编程语言中使用最频繁的一种基础数据类型。如果使用不慎,我们就会为一次字符串的操作所带来的额外性能开销而付出代价。本条建议将从两个方面来探讨如何规避这类性能开销:

    1、确保尽量少的装箱

    2、避免分配额外的内存空间

    先来介绍第一个方面,请看下面的两行代码:

    String str1="str1"+9;
    String str2="str2"+9.ToString();

    从IL代码可以得知,第一行代码在运行时完成一次装箱的行为,而第二行代码中并没有发生装箱的行为,它实际调用的是整型的ToString()方法,效率要比装箱高。所以,在使用其他值引用类型到字符串的转换并完成拼接时,应当避免使用操作符“+”来完成,而应该使用值引用类型提供的ToString()方法。

    第二方面,避免分配额外的内存空间。对CLR来说,string对象(字符串对象)是个很特殊的对象,它一旦被赋值就不可改变。在运行时调用System.String类中的任何方法或进行任何运算(如“=”赋值、“+”拼接等),都会在内存中创建一个新的字符串对象,这也意味着要为该新对象分配新的内存空间。像下面的代码就会带来运行时的额外开销。


    private static void NewMethod1()
    {
          string s1="abc";
          s1="123"+s1+"456";    ////以上两行代码创建了3个字符串对象对象,并执行了一次string.Contact方法           
    }

    private static void NewMethod2()
    {
          string re=9+"456";    ////该方法发生了一次装箱,并调用一次string.Contact方法 
    }

    而以下代码,字符串不会在运行时进行拼接,而是会在编译时直接生成一个字符串。


    private static void NewMethod3()
    {
           string re2="123"+"abc"+"456";   ///该代码等效于///string re2="123abc456"; 
    }

    private static void NewMethod4()
    {
          const string a="t";
          string re="abc"+a;   ///因为a是一个常量,所以该代码等效于string=re="abc"+"t";  最终等效于string re="abct"; 

    由于使用System.String类会在某些场合带来明显的性能损耗,所以微软另外提供了一个类型StringBuilder来弥补String的不足。

    StringBuilder并不会重新创建一个string对象,它的效率源于预先以非托管的方式分配内存。如果StringBuilder没有先定义长度,则默认分配的长度为16。当StringBuilder字符串长度小于等于16时,StringBuilder不会重新分配内存;当StringBuilder字符长度大于16小于32时,StringBuilder又会重新分配内存,使之成为16的倍数。在上面的代码中,如果预先判断字符串的长度将大于16,则可以为其设定一个更加合适的长度(如32)。StringBuilder重新分配内存时是按照上次容量加倍进行分配的。当然,我们需要注意,StringBuilder指定的长度要合适,太小了,需要频繁分配内存,太大了,浪费空间。

    查看以下代码,比较下面两种字符串拼接方式,哪种效率更高:
            private static void NewMethod1()
            {
                string a = "t";
                a += "e";
                a += "s";
                a += "t";
            }

            private static void NewMethod2()
            {
                string a = "t";
                string b = "e";
                string c = "s";
                string d = "t";
                string result = a + b + c + d;
            }

    结果可以得知:两者的效率都不高。不要以为前者比后者创建的字符串对象更少,事实上,两者创建的字符串对象相等,且前者进行了3次string.Contact方法调用,比后者还多了两次。

    要完成这样的运行时字符串拼接(注意:是运行时),更佳的做法是使用StringBuilder类型,代码如下所示:
            public static void NewMethod()
            {
                ////定义了四个变量
                string a = "t";
                string b = "e";
                string c = "s";
                string d = "t";
                StringBuilder sb = new StringBuilder(a);
                sb.Append(b);
                sb.Append(c);
                sb.Append(d);
               
                ///提示是运行时,所以没有使用以下代码
                //StringBuilder sb = new StringBuilder("t");
                //sb.Append("e");
                //sb.Append("s");
                //sb.Append("t");
                //string result = sb.ToString();
            }

    微软还提供了另外一个方法来简化这种操作,即使用string.Format方法。string.Format方法在内部使用StringBuilder进行字符串的格式化,代码如下所示:
            public static void NewMethod4()
            {
                string a = "t";
                string b = "e";
                string c = "s";
                string d = "t";
                string result = string.Format("{0}{1}{2}{3}", a, b, c, d);
            }

  • 请您注意

    ·自觉遵守:爱国、守法、自律、真实、文明的原则

    ·尊重网上道德,遵守《全国人大常委会关于维护互联网安全的决定》及中华人民共和国其他各项有关法律法规

    ·严禁发表危害国家安全,破坏民族团结、国家宗教政策和社会稳定,含侮辱、诽谤、教唆、淫秽等内容的作品

    ·承担一切因您的行为而直接或间接导致的民事或刑事法律责任

    ·您在编程中国社区新闻评论发表的作品,本网站有权在网站内保留、转载、引用或者删除

    ·参与本评论即表明您已经阅读并接受上述条款

  • 感谢本文作者
  • 作者头像
  • 昵称:崽崽
  • 加入时间:2013/5/22 0:00:00
  • TA的签名
  • 这家伙很懒,虾米都没写
  • +进入TA的空间
  • 以下内容也很赞哦
分享按钮