天天看点

.NET面试题解析(03)-string与字符串操作

  系列文章目录地址:

.NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引

字符串可以说是C#开发中最常用的类型了,也是对系统性能影响很关键的类型,熟练掌握字符串的操作非常重要。

1.字符串是引用类型类型还是值类型?

2.在字符串连接处理中,最好采用什么方式,理由是什么?

3.使用 StringBuilder时,需要注意些什么问题?

4.以下代码执行后内存中会存在多少个字符串?分别是什么?输出结果是什么?为什么呢?

5.以下代码执行后内存中会存在多少个字符串?分别是什么?输出结果是什么?为什么呢?

6.使用C#实现字符串反转算法,例如:输入"12345", 输出"54321"。

7.下面的代码输出结果?为什么?

string是一个特殊的引用类型,使用上有点像值类型。之所以特殊,也主要是因为string太常用了,为了提高性能及开发方便,对string做了特殊处理,给予了一些专用特性。为了弥补string在字符串连接操作上的一些性能不足,便有了StringBuilder。

.NET面试题解析(03)-string与字符串操作
首先需要明确的,string是一个引用类型,其对象值存储在托管堆中。string的内部是一个char集合,他的长度Length就是字符char数组的字符个数。string不允许使用new string()的方式创建实例,而是另一种更简单的语法,直接赋值(string aa= “000”这一点也类似值类型)。

认识string,先从一个简单的示例代码入手:

<code>上面的输出结果为“000”。</code>

通过前面的值类型与引用类型的文章,我们知道string是一个引用类型,既然是一个引用类型,参数传递的是引用地址,那为什么不是输出“000111”呢?是不是很有值类型的特点呢!这一切的原因源于string类型的两个重要的特性:恒定性与驻留性

.NET面试题解析(03)-string与字符串操作

字符串是不可变的,字符串一经创建,就不会改变,任何改变都会产生新的字符串。比如下面的代码,堆上先创建了字符串s1=”a”,加上一个字符串“b”后,堆上会存在三个个字符串实例,如下图所示。

.NET面试题解析(03)-string与字符串操作

上文中的”任何改变都会产生新的字符串“,包括字符串的一些操作函数,如str1.ToLower,Trim(),Remove(int startIndex, int count),ToUpper()等,都会产生新的字符串,因此在很多编程实践中,对于字符串忽略大小的比较:

.NET面试题解析(03)-string与字符串操作

由于字符串的不变性,在大量使用字符串操作时,会导致创建大量的字符串对象,带来极大的性能损失。因此CLR又给string提供另外一个法宝,就是字符串驻留,先看看下面的代码,字符串s1、s2竟然是同一个对象!

相同的字符串在内存(堆)中只分配一次,第二次申请字符串时,发现已经有该字符串是,直接返回已有字符串的地址,这就是驻留的基本过程。

CLR初始化时会在内存中创建一个驻留池,内部其实是一个哈希表,存储被驻留的字符串和其内存地址。

驻留池是进程级别的,多个AppDomain共享。同时她不受GC控制,生命周期随进程,意思就是不会被GC回收(不回收!难道不会造成内存爆炸吗?不要急,且看下文)

当分配字符串时,首先会到驻留池中查找,如找到,则返回已有相同字符串的地址,不会创建新字符串对象。如果没有找到,则创建新的字符串,并把字符串添加到驻留池中。

如果大量的字符串都驻留到内存里,而得不到释放,不是很容易造成内存爆炸吗,当然不会了?因为不是任何字符串都会驻留,只有通过IL指令<code>ldstr</code>创建的字符串才会留用。

字符串创建的有多种方式,如下面的代码:

其IL代码如下

.NET面试题解析(03)-string与字符串操作

在上面的代码中,出现两个字符串常量,“123”和“abc”,这个两个常量字符串在IL代码中都是通过IL指令<code>ldstr</code>创建的,只有该指令创建的字符串才会被驻留,其他方式产生新的字符串都不会被驻留,也就不会共享字符串了,会被GC正常回收。

那该如何来验证字符串是否驻留呢,string类提供两个静态方法:

String.Intern(string str) 可以主动驻留一个字符串;

String.IsInterned(string str);检测指定字符串是否驻留,如果驻留则返回字符串,否则返回NULL

.NET面试题解析(03)-string与字符串操作

请看下面的示例代码

.NET面试题解析(03)-string与字符串操作

大量的编程实践和意见中,都说大量字符串连接操作,应该使用StringBuilder。相对于string的不可变,StringBuilder代表可变字符串,不会像字符串,在托管堆上频繁分配新对象,StringBuilder是个好同志。

首先StringBuilder内部同string一样,有一个char[]字符数组,负责维护字符串内容。因此,与char数组相关,就有两个很重要的属性:

public int Capacity:StringBuilder的容量,其实就是字符数组的长度。

public int Length:StringBuilder中实际字符的长度,&gt;=0,&lt;=容量Capacity。

StringBuilder之所以比string效率高,主要原因就是不会创建大量的新对象,StringBuilder在以下两种情况下会分配新对象:

追加字符串时,当字符总长度超过了当前设置的容量Capacity,这个时候,会重新创建一个更大的字符数组,此时会涉及到分配新对象。

调用StringBuilder.ToString(),创建新的字符串。

StringBuilder的默认初始容量为16;

使用stringBuilder.Append()追加一个字符串时,当字符数大于16,StringBuilder会自动申请一个更大的字符数组,一般是倍增;

在新的字符数组分配完成后,将原字符数组中的字符复制到新字符数组中,原字符数组就被无情的抛弃了(会被GC回收);

最后把需要追加的字符串追加到新字符数组中;

简单来说,当StringBuilder的容量Capacity发生变化时,就会引起托管对象申请、内存复制等操作,带来不好的性能影响,因此设置合适的初始容量是非常必要的,尽量减少内存申请和对象创建。代码简单来验证一下:

为什么少量字符串不推荐使用StringBuilder呢?因为StringBuilder本身是有一定的开销的,少量字符串就不推荐使用了,使用String.Concat和String.Join更合适。

.NET面试题解析(03)-string与字符串操作

在使用线程锁的时候,不要锁定一个字符串对象,因为字符串的驻留性,可能会引发不可以预料的问题;

理解字符串的不变性,尽量避免产生额外字符串,如:

在处理大量字符串连接的时候,尽量使用StringBuilder,在使用StringBuilder时,尽量设置一个合适的长度初始值;

少量字符串连接建议使用String.Concat和String.Join代替。

引用类型。

少量字符串连接,使用String.Concat,大量字符串使用StringBuilder,因为StringBuilder的性能更好,如果string的话会创建大量字符串对象。

少量字符串时,尽量不要用,StringBuilder本身是有一定性能开销的;

大量字符串连接使用StringBuilder时,应该设置一个合适的容量;

输出结果:

内存中的字符串只有一个“123abc”,第一行代码(string st1 = "123" + "abc"; )常量字符串相加会被编译器优化。由于字符串驻留机制,两个变量st1、st2都指向同一个对象。IL代码如下:

.NET面试题解析(03)-string与字符串操作

和第5题的结果肯定是不一样的,答案留给读者吧,文章太长了,写的好累!

这是一道比较综合的考察字符串操作的题目,答案可以有很多种。通过不同的答题可以看出程序猿的基础水平。下面是网上比较认可的两种答案,效率上都是比较不错的。

还有一个比较简单也挺有效的方法:

输出结果全是True,因为他们都指向同一个字符串实例,使用object声明和string声明在这里并没有区别(string是引用类型)。

使用object声明和string声明到底有没有区别呢?,有点疑惑,一个朋友在面试时面试官有问过这个问题,那个面试官说sa、a是有区别的,且不相等。对于此疑问,欢迎交流。

版权所有,文章来源:http://www.cnblogs.com/anding 个人能力有限,本文内容仅供学习、探讨,欢迎指正、交流。

书籍: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