是的操作字符串,中的字符串

string是C#.net
的简便基本数据类型(CTS中除去接口、类、委托、Object)的绝无仅有引用类型,而且拥有一些奇异的地点,使用不当或许会埋下非常的大的隐患。

近年拜读了陆敏技先生的《编写高品质代码改革C#先后的15捌个提议》,感觉没错,决定把笔记整理三次。

壹 、字符串长度和 字符串内部存款和储蓄器长度

字符串的长短是字符串的字符个数,中文字或标志也算一个字符,例如

string msg=”Hello中国!”;         // 咋舌号为中普通话感叹号

上边字符串长度为 8 ,字符串占用内部存款和储蓄器字节数 :11
(GB2312,3个国语字符占二个字节) ,14(UTF8)
,32(UTF32),可知占用内部存款和储蓄器字节数和编码有关。 那个标题自己看来有店铺面试题里面有(给定答案是2*Len),分明答案是大错特错的。 不点名编码的场所下大家的体系一般默许为GB2312,内部存款和储蓄器长度为11字节,ASCII字符占二个字节,粤语占3个字节。

建议1: 正确操作字符串

贰 、字符串常量

字符串又称之为字符串常量,一旦初阶化后,其值就不会转移。任何对字符串的操作都会产生新的字符串。例如插入、合并等,都会发出新的字符串,原字符串不会变。那点要尤其注意,如若复杂频仍的字符串操作忽略了这一特点恐怕带来内部存款和储蓄器溢出的危害。

字符串应该是独具编程语言中应用最频仍的一种基础数据类型。若是运用不慎,大家就会为一遍字符串的操作所带来的额外性能支付而付出代价。本条提议将从四个地点来研究如何躲避那类质量源消费用:

三 、对象浅拷贝 Object.MemberwiseClone

MemberwiseClone方法用于创建当前指标的外表副本(浅拷贝)。其原理是将生成二个该项指标指标,将近来目的的非静态字段复制到新指标以创设浅表副本。假若字段是值类型,则对该字段进行按位复制,创立1个值副本;就算是1个引用类型,则复制的是引用,也正是说新的表层副本中援引类型字段也是指向原始字段值的2个引用,新对象引用类型字段的改动会潜移默化原有对象的应和字段。
但是,对接纳项目字段的平整不适用于字符串,字符串类型字段在新目的中保留的是原始字符串的副本,所以其修改不影响原本对象。

  • 担保尽量少的装箱
  • 防止分配额外的内部存款和储蓄器空间

四 、操作品质

支出进度中平日会对字符串实行操作,比如联结()Contact,这么些时候一定要考虑该操作供给分配的空中,生成的靶子个数,不然也许带来灾祸性结果,而且一旦没有那种发现,那种题材比较能检查。

翻开上边三种艺术:

public string CombineA()

{

       string name=”尼古拉”;

       return  name + “  · 耶维奇 ·  ” + “奥斯特洛夫斯基”;

}

public string CombineB()

{

      return =”Nikola” + “  · 耶维奇 ·  ” + “奥斯特洛夫斯基”;

}

public string CombineC()

{

       return  1+ “  · 耶维奇 ·  ” + “奥斯特洛夫斯基”;

}

办法CombineA创制了叁个字符串对象”Nikola” , “ · 耶维奇 · ”
,“奥斯特洛夫斯基” , 执行了 1 次String.Contact操作;

办法CombineB创造了3个字符串对象 “Nikola · 耶维奇 ·  奥斯特洛夫斯基”
,没有进行String.Contact操作。

方法CombineC创立了一个字符串对象“· 耶维奇 · 
奥斯特洛夫斯基”和分配类三个整型数空间,进行了一次装箱操作和3次String.Contact操作。

实际区分以上二种方式的最重要就是心心念念一句话“字符串是常量,一旦起头化就不会变”;方法B中实际是编写翻译时候已经优化。

先来介绍第三个方面,请看上面包车型客车两行代码:

标准化:数十次的字符串联结操作不要用“+”或Contact方法,请改用StringBuilder对象操作, StringBuilder不会发生新的字符串空间分配;

字符串的联结会爆发新字符串能源分配,如若在一个循环中实行操作,大概导致内部存款和储蓄器溢出(字符串分配到大指标堆上)。举二个例证,Socket接收音信,输出到文本框中,尽管使用上边写法,运维一段时间程序就会内部存款和储蓄器溢出:

        txtMsg.Text+=receivedMsg;

原因是那种操作会分配新的字符串空间,随着时光该字符串会更为大,结果由此可见。要缓解那一个难点,可以选用下边方法替换:

        txtMsg.AppendText(receivedMsg);   
//该情势操作原理和StringBuilder.Append() 一样

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

 

为了驾驭那两行代码的施市场价格况,大家来相比较两者生成的IL代码。

第二行代码对应的IL代码如下:

    .maxstack  8  
    IL_0000:  ldstr      "str1"  
    IL_0005:  ldc.i4.s   9  
    IL_0007:  box        [mscorlib]System.Int32  
    IL_000c:  call       string [mscorlib]System.String::Concat(object, object)  
    IL_0011:  pop  
    IL_0012:  ret 

 

其次行代码对应的IL代码如下:

    .maxstack  2  
    .locals init ([0] int32 CS$0$0000)  
    IL_0000:  ldstr      "str2"  
    IL_0005:  ldc.i4.s   9  
    IL_0007:  stloc.0  
    IL_0008:  ldloca.s   CS$0$0000  
    IL_000a:  call       instance string [mscorlib]System.Int32::ToString()  
    IL_000f:  call       string [mscorlib]System.String::Concat(string, string)  
    IL_0014:  pop  
    IL_0015:  ret 

 

能够见见,第③行代码“str1”+
9在运行时会实现二遍装箱行为(IL代码中的box);而第①行代码中的9.ToString()并没有产生装箱行为,它实在调用的是整型的ToString方法。ToString方法的原型为:

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

 

或许有人会问,是否原型中的Number.FormatInt32方法会爆发装箱行为呢?实际上,Number.FormatInt32主意是3个非托管的办法,其原型如下:

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

 

它是透过直接操作内部存款和储蓄器来达成从int到string的转移,效能要比装箱高很多。所以,在使用别的值引用类型到字符串的变换并成功拼接时,应当幸免使用操作符“+”来成功,而应当选拔值引用类型提供的ToString方法。

或许有人还会提出难点:上文所举的言传身教中,尽管FCL提供的格局没有生出装箱行为,但在别的景况下,FCL方法内部会不会蕴藏装箱的一颦一笑呢?答案是:只怕会存在。然而,我们那里有1个携带规范:

在友好编排的代码中,应当尽大概地幸免编写制定不须求的装箱代码。

只顾 装箱之所以会带来品质损耗,因为它必要形成上面多个步骤:

1)首先,会为值类型在托管堆中分配内部存款和储蓄器。除了值类型自身所分配的内部存款和储蓄器外,内部存款和储蓄器总量还要加上项目对象指针和协助举行块索引所占用的内部存款和储蓄器。

2)将值类型的值复制到新分配的堆内部存储器中。

3)重临已经成为引用类型的指标的地址。

其次个地点:幸免分分配的定额外的内部存款和储蓄器空间。对CL瑞鹰来说,string对象(字符串对象)是个很新鲜的对象,它一旦被赋值就不可变更。在运营时调用
System.String
类中的任何格局或开始展览任何运算(如“=”赋值、“+”拼接等),都会在内部存款和储蓄器中成立五个新的字符串对象,那也表示要为该新对象分配新的内部存款和储蓄器空间。像下边包车型地铁代码就会带来运转时的额外开支。

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

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

 

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

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

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

 

由于使用 System.String
类会在少数场面带来显然的品质损耗,所以微软此外提供了一个品类StringBuilder来弥补String的欠缺。

StringBuilder并不会再度创立2个string
对象,它的频率源于预先以非托管的不二法门分配内部存款和储蓄器。若是StringBuilder
没有先定义长度,则暗中认可分配的尺寸为16。当 StringBuilder 字符长度小于等于
16时,StringBuilder 不会重新分配内部存储器;当 StringBuilder 字符长度大于16
小于 32时,StringBuilder 又会重新分配内部存款和储蓄器,使之变成
16的翻番。在上头的代码中,假诺预先判断字符串的长短将超越16,则足以为其设定贰个越来越方便的尺寸(如32)。StringBuilder重新分配内部存储器时是遵从上次的体量加倍举行分配的。当然,大家须求小心,StringBuilder钦定的长度要适当,太小了,须要反复分配内部存款和储蓄器;太大了,浪费空间。

已经有人问小编,上面包车型地铁三种字符串拼接方式,哪个种类效能更高:

    1.      private static void NewMethod8()  
            {  
                string a = "t";  
                a += "e";  
                a += "s";  
                a += "t";  
            }  

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

 

答案是:两者功能都不高。不要觉得前者比继承者创设的字符串对象更少,事实上,两者创制的字符串对象相等,且前者进行了二次string.Contact方法调用,比后者还多了三遍。

要到位如此的运维时字符串拼接(注意:是运转时),更佳的做法是应用StringBuilder类型,代码如下所示:

    private static void NewMethod10()  
    {  
        //为了演示的需要,定义了4个变量  
        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进行字符串的格式化,如下边包车型大巴代码所示:

    private static void NewMethod11()  
    {  
        //为了演示的需要,定义了4个变量  
        string a = "t";  
        string b = "e";  
        string c = "s";  
        string d = "t";  
        string.Format("{0}{1}{2}{3}", a, b, c, d);  
    }

 

转自:《编写高品质代码改进C#程序的157个建议》陆敏技