string是C#.net 的简单基本数据类型(CTS中除了接口、类、委托、Object)的唯一引用类型,而且有着一些特殊的地方,使用不当可能会埋下很大的隐患。
1、字符串长度和 字符串内存长度
字符串的长度是字符串的字符个数,中文字或符号也算一个字符,例如
string msg=”Hello中国!”; // 感叹号为中中文感叹号
上面字符串长度为 8 ,字符串占用内存字节数 :11 (GB2312,一个中文字符占2个字节) ,14(UTF8) ,32(UTF32),可见占用内存字节数和编码有关。 这个问题我见到有公司面试题里面有(给定答案是2*Len),显然答案是错误的。 不指定编码的情况下我们的系统一般默认为GB2312,内存长度为11字节,ASCII字符占一个字节,中文占2个字节。
2、字符串常量
字符串又称为字符串常量,一旦初始化后,其值就不会改变。任何对字符串的操作都会产生新的字符串。例如插入、合并等,都会产生新的字符串,原字符串不会变。这一点要特别注意,如果复杂频繁的字符串操作忽略了这一特点可能带来内存溢出的风险。
3、对象浅拷贝 Object.MemberwiseClone
MemberwiseClone方法用于创建当前对象的浅表副本(浅拷贝)。其原理是将生成一个该类型的对象,将当前对象的非静态字段复制到新对象以创建浅表副本。如果字段是值类型,则对该字段进行按位复制,创建一个值副本;如果是一个引用类型,则复制的是引用,也就是说新的浅表副本中引用类型字段也是指向原始字段值的一个引用,新对象引用类型字段的修改会影响原始对象的相应字段。 但是,对应用类型字段的规则不适用于字符串,字符串类型字段在新对象中保存的是原始字符串的副本,所以其修改不影响原始对象。
4、操作性能
开发过程中经常会对字符串进行操作,比如联结()Contact,这个时候一定要考虑该操作需要分配的空间,生成的对象个数,否则可能带来灾难性结果,而且如果没有这种意识,这种问题比较能检查。
查看下面两种方法:
public string CombineA()
{
string name=”尼古拉”;
return name + “ · 耶维奇 · ” + “奥斯特洛夫斯基”;
}
public string CombineB()
{
return =”尼古拉” + “ · 耶维奇 · ” + “奥斯特洛夫斯基”;
}
public string CombineC()
{
return 1+ “ · 耶维奇 · ” + “奥斯特洛夫斯基”;
}
方法CombineA创建了3个字符串对象”尼古拉” , “ · 耶维奇 · ” ,“奥斯特洛夫斯基” , 执行了 1 次String.Contact操作;
方法CombineB创建了一个字符串对象 “尼古拉 · 耶维奇 · 奥斯特洛夫斯基” ,没有执行String.Contact操作。
方法CombineC创建了一个字符串对象“· 耶维奇 · 奥斯特洛夫斯基”和分配类一个整型数空间,进行了一次装箱操作和一次String.Contact操作。
其实区分以上三种方法的关键就是记住一句话“字符串是常量,一旦初始化就不会变”;方法B中其实是编译时候已经优化。
原则:多次的字符串联结操作不要用“+”或Contact方法,请改用StringBuilder对象操作, StringBuilder不会产生新的字符串空间分配;
字符串的联结会产生新字符串资源分配,如果在一个循环中进行操作,可能造成内存溢出(字符串分配到大对象堆上)。举一个例子,Socket接收信息,输出到文本框中,如果使用下面写法,运行一段时间程序就会内存溢出:
txtMsg.Text+=receivedMsg;
原因是这种操作会分配新的字符串空间,随着时间该字符串会越来越大,结果可想而知。要解决这个问题,可以使用下面方法替换:
txtMsg.AppendText(receivedMsg); //该方法操作原理和StringBuilder.Append() 一样