zoukankan      html  css  js  c++  java
  • 正确的使用字符串String

    字符串作为所有编程语言中使用最频繁的一种基础数据类型。如果使用不慎,将会造成不必要的内存开销,为此而付出代价。而要优化此类型,从以下两点入手:

    1、尽量少的装箱

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

    先从第一点装箱的操作说起,查看如下代码:

     //发生装箱的代码
     String boxOperate = "test" + 4.5f;

    其中间语言IL代码为如下:

        IL_0000: nop
        IL_0001: ldstr "test"
        IL_0006: ldc.r4 4.5
        IL_000b: box [mscorlib]System.Single
        IL_0010: call string [mscorlib]System.String::Concat(object, object)
        IL_0015: stloc.0
        IL_0016: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
        IL_001b: pop
        IL_001c: ret

    不难看出,上述代码发生了装箱的操作(IL代码中的box).装箱之所以会发生性能损耗,因为它要完成如下三个步骤:

    1、首先,会为值类型在托管堆中分配内存。除了值类型本身所分配的内存外,内存总量还要加上类型对象指针和同步块索引所占用的内存,

    2、将值类型的值复制到新分配的堆内存中。

    3、返回已经成为引用类型的对象的地址。

    在来看以下代码:

    //没有发生装箱的代码
     String boxOperate = "test" + 4.ToString();

    其中间IL代码如下:

        IL_0000: nop
        IL_0001: ldstr "test"
        IL_0006: ldc.r4 4
        IL_000b: stloc.1
        IL_000c: ldloca.s 1
        IL_000e: call instance string [mscorlib]System.Single::ToString()
        IL_0013: call string [mscorlib]System.String::Concat(string, string)
        IL_0018: stloc.0
        IL_0019: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
        IL_001e: pop
        IL_001f: ret

    如上,并没有发生任何装箱操作,但是达到的结果却是我们想要的。原因是 4.ToString() 这行代码并没有发生装箱行为,是实际调用的是整数型的ToString()方法,其原型如下:

     public override string ToString(){
         return Number.FormatInt32(m_value, null, NumberFormat.CurrentInfo);
      }

    可能有人会问,是不是原型中的 Number.Format_XXX方法会发生装箱行为呢?实际上,Number.Format_XXX方法是一个非托管的方法,其原型如下:

    [MethodImpl(MethodImplOptions.InternalCall), SecurityCritical]
    public statuc extern string FormatInt32(int value, string format,NumberFormatInfo info);

    它是通过直接操作内存来完成 Int32 到 String 的转换,效率要比装箱高得多。所以,在使用其他值引用类型到字符串得转换比完成拼接时,应当避免使用操作符 “+” 来我完成,而应该使用值引用类型提供得ToString方法。

    也许有人会问:即使FCL提供得方法没有发生装箱行为,但在其他情况下,FCL方法内部会不会含有装箱的行为?也许会存在,所以,本人推荐:编写代码中,应当尽量避免发生不必要的装箱代码。

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

    private static void Test()
    {   String str1
    = "aa"; str1 = str1 + "123" + "345"; //以上代码创建了3个String对象,并执行了一次String.Contact方法。 }

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

    private static void Test()
    {
    String str= "aa" + "123" + "345";//等效 String str= "aa123345";
    }

    private static void Test2()
    {
    const String str = "aa";
    String newStr = "123" + str;
    //因为str是一个常量,所以该代码等效于 String newStr = "123" + “aa”;
    //最终等效于 String newStr = "123aa”;
    }

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

           StringBuilder并不会重新创建一个String对象,它的效率源于预先以非托管的方式分配内存。如果StringBuilder没有先定义长度,则默认分配的长度为16。当StringBuilder的长度大于16小于32时,StringBuild又会重新分配内存,使之成为16的倍数。StringBuilder重新分配内存时按照上次的容量加倍进行分配的。注意:StringBuilder指定的长度要合适,太小了,需要频繁分配内存;太大了,浪费内存空间。

      以下是例子举例:

            private static String Test3()
            {
                String a = "t";
                a += "e";
                a += "s";
                a += "t";
                return a;
            }
            private static String Test4()
            {
                String a = "t";
                String b = "e";
                String c = "s";
                String d = "t";
                return a + b + c + d;
            }
            //以上两种效率都不高效。不要以为前者比后者创建的字符串对象更少,事实上,两者创建的字符串对象相等
            //且前者进行了3次的String.Contact方法调用,比后者还多了两次。

    要完成上图的运行时的字符串拼接(注意:是运行时),更佳的做法是使用StringBuilder类型,代码如下:

            private static String Test5()
            {
                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);
                return sb.ToString();
                //因为说的是运行时,所以没必要使用以下代码
                //StringBuilder sb = new StringBuilder("t");
                //sb.Append("e");
                //sb.Append("s");
                //sb.Append("t");
                //return sb.ToString();
            }

    微软还提供了另外一个来简化这种操作,即使用String.Format 方法。String.Format方法在内部使用StringBuilder 进行字符串格式化,如下图代码:

    private static String Test6()
    {
          //为演示,定义4个变量
          String a = "t";
          String b = "e";
          String c = "s";
          String d = "t";
          return String.Format("{0}{1}{2}{3}", a, b, c, d);
    }

     总结:在使用String字符串时,应该尽量避免装箱操作和“+”连接操作。

    此次随笔结束!这是本人第一次写博客,如有什么错误的解释,欢迎批评指正。

  • 相关阅读:
    只打开一次浏览器,生成html测试报告<小紧张中......>
    hadoop学习第四天-Writable和WritableComparable序列化接口的使用&&MapReduce中传递javaBean的简单例子
    hadoop学习第三天-MapReduce介绍&&WordCount示例&&倒排索引示例
    hadoop学习第二天-了解HDFS的基本概念&&分布式集群的搭建&&HDFS基本命令的使用
    hadoop学习第一天-hadoop初步环境搭建&伪分布式计算配置(详细)
    大三了,写写博客
    方向问题
    第一天注册博客
    jquery跨域请求事例
    ECharts学习记录
  • 原文地址:https://www.cnblogs.com/fode/p/10061233.html
Copyright © 2011-2022 走看看