字符串能够说是C#开发中最经常使用的类型了,也是对系统性能影响很关键的类型,熟练掌握字符串的操做很是重要。html
1.字符串是引用类型类型仍是值类型?面试
2.在字符串链接处理中,最好采用什么方式,理由是什么?算法
3.使用 StringBuilder时,须要注意些什么问题?编程
4.如下代码执行后内存中会存在多少个字符串?分别是什么?输出结果是什么?为何呢?数组
string st1 = "123" + "abc"; string st2 = "123abc"; Console.WriteLine(st1 == st2); Console.WriteLine(System.Object.ReferenceEquals(st1, st2));
5.如下代码执行后内存中会存在多少个字符串?分别是什么?输出结果是什么?为何呢?函数
string s1 = "123"; string s2 = s1 + "abc"; string s3 = "123abc"; Console.WriteLine(s2 == s3); Console.WriteLine(System.Object.ReferenceEquals(s2, s3));
6.使用C#实现字符串反转算法,例如:输入"12345", 输出"54321"。性能
7.下面的代码输出结果?为何?学习
object a = "123"; object b = "123"; Console.WriteLine(System.Object.Equals(a,b)); Console.WriteLine(System.Object.ReferenceEquals(a,b)); string sa = "123"; Console.WriteLine(System.Object.Equals(a, sa)); Console.WriteLine(System.Object.ReferenceEquals(a, sa));
string是一个特殊的引用类型,使用上有点像值类型。之因此特殊,也主要是由于string太经常使用了,为了提升性能及开发方便,对string作了特殊处理,给予了一些专用特性。为了弥补string在字符串链接操做上的一些性能不足,便有了StringBuilder。优化
首先须要明确的,string是一个引用类型,其对象值存储在托管堆中。string的内部是一个char集合,他的长度Length就是字符char数组的字符个数。string不容许使用new string()的方式建立实例,而是另外一种更简单的语法,直接赋值(string aa= “000”这一点也相似值类型)。ui
认识string,先从一个简单的示例代码入手:
public void DoStringTest() { var aa = "000"; SetStringValue(aa); Console.WriteLine(aa); } private void SetStringValue(string aa) { aa += "111"; }
上面的输出结果为“000”。
经过前面的值类型与引用类型的文章,咱们知道string是一个引用类型,既然是一个引用类型,参数传递的是引用地址,那为何不是输出“000111”呢?是否是颇有值类型的特色呢!这一切的缘由源于string类型的两个重要的特性:恒定性与驻留性
字符串是不可变的,字符串一经建立,就不会改变,任何改变都会产生新的字符串。好比下面的代码,堆上先建立了字符串s1=”a”,加上一个字符串“b”后,堆上会存在三个个字符串实例,以下图所示。
string s1 = "a"; string s2 = s1 + "b";
上文中的”任何改变都会产生新的字符串“,包括字符串的一些操做函数,如str1.ToLower,Trim(),Remove(int startIndex, int count),ToUpper()等,都会产生新的字符串,所以在不少编程实践中,对于字符串忽略大小的比较:
if(str1.ToLower()==str2.ToLower()) //这种方式会产生新的字符串,不推荐 if(string. Compare(str1,str2,true)) //这种方式性能更好
因为字符串的不变性,在大量使用字符串操做时,会致使建立大量的字符串对象,带来极大的性能损失。所以CLR又给string提供另一个法宝,就是字符串驻留,先看看下面的代码,字符串s一、s2居然是同一个对象!
var s1 = "123"; var s2 = "123"; Console.WriteLine(System.Object.Equals(s1, s2)); //输出 True Console.WriteLine(System.Object.ReferenceEquals(s1, s2)); //输出 True
相同的字符串在内存(堆)中只分配一次,第二次申请字符串时,发现已经有该字符串是,直接返回已有字符串的地址,这就是驻留的基本过程。
若是大量的字符串都驻留到内存里,而得不到释放,不是很容易形成内存爆炸吗,固然不会了?由于不是任何字符串都会驻留,只有经过IL指令ldstr
建立的字符串才会留用。
字符串建立的有多种方式,以下面的代码:
var s1 = "123"; var s2 = s1 + "abc"; var s3 = string.Concat(s1, s2); var s4 = 123.ToString(); var s5 = s2.ToUpper();
其IL代码以下
在上面的代码中,出现两个字符串常量,“123”和“abc”,这个两个常量字符串在IL代码中都是经过IL指令ldstr
建立的,只有该指令建立的字符串才会被驻留,其余方式产生新的字符串都不会被驻留,也就不会共享字符串了,会被GC正常回收。
那该如何来验证字符串是否驻留呢,string类提供两个静态方法:
请看下面的示例代码
var s1 = "123"; var s2 = s1 + "abc"; Console.WriteLine(s2); //输出:123abc Console.WriteLine(string.IsInterned(s2) ?? "NULL"); //输出:NULL。由于“123abc”没有驻留 string.Intern(s2); //主动驻留字符串 Console.WriteLine(string.IsInterned(s2) ?? "NULL"); //输出:123abc
大量的编程实践和意见中,都说大量字符串链接操做,应该使用StringBuilder。相对于string的不可变,StringBuilder表明可变字符串,不会像字符串,在托管堆上频繁分配新对象,StringBuilder是个好同志。
首先StringBuilder内部同string同样,有一个char[]字符数组,负责维护字符串内容。所以,与char数组相关,就有两个很重要的属性:
StringBuilder之因此比string效率高,主要缘由就是不会建立大量的新对象,StringBuilder在如下两种状况下会分配新对象:
简单来讲,当StringBuilder的容量Capacity发生变化时,就会引发托管对象申请、内存复制等操做,带来很差的性能影响,所以设置合适的初始容量是很是必要的,尽可能减小内存申请和对象建立。代码简单来验证一下:
StringBuilder sb1 = new StringBuilder(); Console.WriteLine("Capacity={0}; Length={1};", sb1.Capacity, sb1.Length); //输出:Capacity=16; Length=0; //初始容量为16 sb1.Append('a', 12); //追加12个字符 Console.WriteLine("Capacity={0}; Length={1};", sb1.Capacity, sb1.Length); //输出:Capacity=16; Length=12; sb1.Append('a', 20); //继续追加20个字符,容量倍增了 Console.WriteLine("Capacity={0}; Length={1};", sb1.Capacity, sb1.Length); //输出:Capacity=32; Length=32; sb1.Append('a', 41); //追加41个字符,新容量=32+41=73 Console.WriteLine("Capacity={0}; Length={1};", sb1.Capacity, sb1.Length); //输出:Capacity=73; Length=73; StringBuilder sb2 = new StringBuilder(80); //设置一个合适的初始容量 Console.WriteLine("Capacity={0}; Length={1};", sb2.Capacity, sb2.Length); //输出:Capacity=80; Length=0; sb2.Append('a', 12); Console.WriteLine("Capacity={0}; Length={1};", sb2.Capacity, sb2.Length); //输出:Capacity=80; Length=12; sb2.Append('a', 20); Console.WriteLine("Capacity={0}; Length={1};", sb2.Capacity, sb2.Length); //输出:Capacity=80; Length=32; sb2.Append('a', 41); Console.WriteLine("Capacity={0}; Length={1};", sb2.Capacity, sb2.Length); //输出:Capacity=80; Length=73;
为何少许字符串不推荐使用StringBuilder呢?由于StringBuilder自己是有必定的开销的,少许字符串就不推荐使用了,使用String.Concat和String.Join更合适。
if(str1.ToLower()==str2.ToLower()) //这种方式会产生新的字符串,不推荐 if(string. Compare(str1,str2,true)) //这种方式性能更好
引用类型。
少许字符串链接,使用String.Concat,大量字符串使用StringBuilder,由于StringBuilder的性能更好,若是string的话会建立大量字符串对象。
string st1 = "123" + "abc"; string st2 = "123abc"; Console.WriteLine(st1 == st2); Console.WriteLine(System.Object.ReferenceEquals(st1, st2));
输出结果:
True
True
内存中的字符串只有一个“123abc”,第一行代码(string st1 = "123" + "abc"; )常量字符串相加会被编译器优化。因为字符串驻留机制,两个变量st一、st2都指向同一个对象。IL代码以下:
string s1 = "123"; string s2 = s1 + "abc"; string s3 = "123abc"; Console.WriteLine(s2 == s3); Console.WriteLine(System.Object.ReferenceEquals(s2, s3));
和第5题的结果确定是不同的,答案留给读者吧,文章太长了,写的好累!
这是一道比较综合的考察字符串操做的题目,答案能够有不少种。经过不一样的答题能够看出程序猿的基础水平。下面是网上比较承认的两种答案,效率上都是比较不错的。
public static string Reverse(string str) { if (string.IsNullOrEmpty(str)) { throw new ArgumentException("参数不合法"); } StringBuilder sb = new StringBuilder(str.Length); //注意:设置合适的初始长度,能够显著提升效率(避免了屡次内存申请) for (int index = str.Length - 1; index >= 0; index--) { sb.Append(str[index]); } return sb.ToString(); }
public static string Reverse(string str) { if (string.IsNullOrEmpty(str)) { throw new ArgumentException("参数不合法"); } char[] chars = str.ToCharArray(); int begin = 0; int end = chars.Length - 1; char tempChar; while (begin < end) { tempChar = chars[begin]; chars[begin] = chars[end]; chars[end] = tempChar; begin++; end--; } string strResult = new string(chars); return strResult; }
还有一个比较简单也挺有效的方法:
public static string Reverse(string str) { char[] arr = str.ToCharArray(); Array.Reverse(arr); return new string(arr); }
object a = "123"; object b = "123"; Console.WriteLine(System.Object.Equals(a,b)); Console.WriteLine(System.Object.ReferenceEquals(a,b)); string sa = "123"; Console.WriteLine(System.Object.Equals(a, sa)); Console.WriteLine(System.Object.ReferenceEquals(a, sa));
输出结果全是True,由于他们都指向同一个字符串实例,使用object声明和string声明在这里并无区别(string是引用类型)。
使用object声明和string声明到底有没有区别呢?,有点疑惑,一个朋友在面试时面试官有问过这个问题,那个面试官说sa、a是有区别的,且不相等。对于此疑问,欢迎交流。
版权全部,文章来源:http://www.cnblogs.com/anding
我的能力有限,本文内容仅供学习、探讨,欢迎指正、交流。
.NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引
书籍:CLR via C#
书籍:你必须知道的.NET
深刻理解string和如何高效地使用string: http://www.cnblogs.com/artech/archive/2007/05/06/737130.html
C#基础知识梳理系列九:StringBuilder:http://www.cnblogs.com/solan/archive/2012/08/06/CSharp09.html