正確的使用字符串String

字符串做爲全部編程語言中使用最頻繁的一種基礎數據類型。若是使用不慎,將會形成沒必要要的內存開銷,爲此而付出代價。而要優化此類型,從如下兩點入手:編程

一、儘可能少的裝箱編程語言

二、避免分配額外的內存空間ide

先從第一點裝箱的操做提及,查看以下代碼:性能

 //發生裝箱的代碼
 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).裝箱之因此會發生性能損耗,由於它要完成以下三個步驟:ui

一、首先,會爲值類型在託管堆中分配內存。除了值類型自己所分配的內存外,內存總量還要加上類型對象指針和同步塊索引所佔用的內存,spa

二、將值類型的值複製到新分配的堆內存中。指針

三、返回已經成爲引用類型的對象的地址。code

 

在來看如下代碼:orm

//沒有發生裝箱的代碼
 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字符串時,應該儘可能避免裝箱操做和「+」鏈接操做。

這次隨筆結束!這是本人第一次寫博客,若有什麼錯誤的解釋,歡迎批評指正。

相關文章
相關標籤/搜索