.NET面试题4
常见面试题目:
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是一个引用类型,其对象值存储在托管堆中。string的内部是一个char集合,他的长度Length就是字符char数组的字符个数。string不允许使用new string()的方式创建实例,而是另一种更简单的语法,直接赋值(string aa= “000”这一点也类似值类型)。
认识string,先从一个简单的示例代码入手:
public void DoStringTest()
{
var aa = "000";
SetStringValue(aa);
Console.WriteLine(aa);
} private void SetStringValue(string aa)
{
aa += "111";
}
上面的输出结果为“000”。
通过前面的值类型与引用类型的文章,我们知道string是一个引用类型,既然是一个引用类型,参数传递的是引用地址,那为什么不是输出“000111”呢?是不是很有值类型的特点呢!这一切的原因源于string类型的两个重要的特性:恒定性与驻留性
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)) //这种方式性能更好
String的驻留性
由于字符串的不变性,在大量使用字符串操作时,会导致创建大量的字符串对象,带来极大的性能损失。因此CLR又给string提供另外一个法宝,就是字符串驻留,先看看下面的代码,字符串s1、s2竟然是同一个对象!
var s1 = "123";
var s2 = "123";
Console.WriteLine(System.Object.Equals(s1, s2)); //输出 True
Console.WriteLine(System.Object.ReferenceEquals(s1, s2)); //输出 True
相同的字符串在内存(堆)中只分配一次,第二次申请字符串时,发现已经有该字符串是,直接返回已有字符串的地址,这就是驻留的基本过程。
字符串驻留的基本原理:
- CLR初始化时会在内存中创建一个驻留池,内部其实是一个哈希表,存储被驻留的字符串和其内存地址。
- 驻留池是进程级别的,多个AppDomain共享。同时她不受GC控制,生命周期随进程,意思就是不会被GC回收(不回收!难道不会造成内存爆炸吗?不要急,且看下文)
- 当分配字符串时,首先会到驻留池中查找,如找到,则返回已有相同字符串的地址,不会创建新字符串对象。如果没有找到,则创建新的字符串,并把字符串添加到驻留池中。
如果大量的字符串都驻留到内存里,而得不到释放,不是很容易造成内存爆炸吗,当然不会了?因为不是任何字符串都会驻留,只有通过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类提供两个静态方法:
- String.Intern(string str) 可以主动驻留一个字符串;
- String.IsInterned(string str);检测指定字符串是否驻留,如果驻留则返回字符串,否则返回NULL
请看下面的示例代码
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
大量的编程实践和意见中,都说大量字符串连接操作,应该使用StringBuilder。相对于string的不可变,StringBuilder代表可变字符串,不会像字符串,在托管堆上频繁分配新对象,StringBuilder是个好同志。
首先StringBuilder内部同string一样,有一个char[]字符数组,负责维护字符串内容。因此,与char数组相关,就有两个很重要的属性:
- public int Capacity:StringBuilder的容量,其实就是字符数组的长度。
- public int Length:StringBuilder中实际字符的长度,>=0,<=容量Capacity。
StringBuilder之所以比string效率高,主要原因就是不会创建大量的新对象,StringBuilder在以下两种情况下会分配新对象:
- 追加字符串时,当字符总长度超过了当前设置的容量Capacity,这个时候,会重新创建一个更大的字符数组,此时会涉及到分配新对象。
- 调用StringBuilder.ToString(),创建新的字符串。
追加字符串的过程:
- StringBuilder的默认初始容量为16;
- 使用stringBuilder.Append()追加一个字符串时,当字符数大于16,StringBuilder会自动申请一个更大的字符数组,一般是倍增;
- 在新的字符数组分配完成后,将原字符数组中的字符复制到新字符数组中,原字符数组就被无情的抛弃了(会被GC回收);
- 最后把需要追加的字符串追加到新字符数组中;
简单来说,当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)) //这种方式性能更好
- 在处理大量字符串连接的时候,尽量使用StringBuilder,在使用StringBuilder时,尽量设置一个合适的长度初始值;
- 少量字符串连接建议使用String.Concat和String.Join代替。
题目答案解析:
1.字符串是引用类型类型还是值类型?
引用类型。
2.在字符串连加处理中,最好采用什么方式,理由是什么?
少量字符串连接,使用String.Concat,大量字符串使用StringBuilder,因为StringBuilder的性能更好,如果string的话会创建大量字符串对象。
3.使用 StringBuilder时,需要注意些什么问题?
- 少量字符串时,尽量不要用,StringBuilder本身是有一定性能开销的;
- 大量字符串连接使用StringBuilder时,应该设置一个合适的容量;
4.以下代码执行后内存中会存在多少个字符串?分别是什么?输出结果是什么?为什么呢?
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"; )常量字符串相加会被编译器优化。由于字符串驻留机制,两个变量st1、st2都指向同一个对象。IL代码如下:
5.以下代码执行后内存中会存在多少个字符串?分别是什么?输出结果是什么?为什么呢?
string s1 = "123";
string s2 = s1 + "abc";
string s3 = "123abc";
Console.WriteLine(s2 == s3);
Console.WriteLine(System.Object.ReferenceEquals(s2, s3));
和第5题的结果肯定是不一样的,答案留给读者吧,文章太长了,写的好累!
6.使用C#实现字符串反转算法,例如:输入"12345", 输出"54321"
这是一道比较综合的考察字符串操作的题目,答案可以有很多种。通过不同的答题可以看出程序猿的基础水平。下面是网上比较认可的两种答案,效率上都是比较不错的。
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);
}
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));
输出结果全是True,因为他们都指向同一个字符串实例,使用object声明和string声明在这里并没有区别(string是引用类型)。
使用object声明和string声明到底有没有区别呢?,有点疑惑,一个朋友在面试时面试官有问过这个问题,那个面试官说sa、a是有区别的,且不相等。对于此疑问,欢迎交流。
版权所有,文章来源:http://www.cnblogs.com/anding
个人能力有限,本文内容仅供学习、探讨,欢迎指正、交流。
.NET面试题4的更多相关文章
- .NET面试题系列[8] - 泛型
“可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用.“ - Jon Skeet .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] ...
- 关于面试题 Array.indexof() 方法的实现及思考
这是我在面试大公司时碰到的一个笔试题,当时自己云里雾里的胡写了一番,回头也曾思考过,最终没实现也就不了了之了. 昨天看到有网友说面试中也碰到过这个问题,我就重新思考了这个问题的实现方法. 对于想进大公 ...
- 对Thoughtworks的有趣笔试题实践
记得2014年在网上看到Thoughtworks的一道笔试题,当时觉得挺有意思,但是没动手去写.这几天又在网上看到了,于是我抽了一点时间写了下,我把程序运行的结果跟网上的答案对了一下,应该是对的,但是 ...
- 从阿里巴巴笔试题看Java加载顺序
一.阿里巴巴笔试题: public class T implements Cloneable { public static int k = 0; public static T t1 = new T ...
- JAVA面试题
在这里我将收录我面试过程中遇到的一些好玩的面试题目 第一个面试题:ABC问题,有三个线程,工作的内容分别是打印出"A""B""C",需要做的 ...
- C++常考面试题汇总
c++面试题 一 用简洁的语言描述 c++ 在 c 语言的基础上开发的一种面向对象编程的语言: 应用广泛: 支持多种编程范式,面向对象编程,泛型编程,和过程化编程:广泛应用于系统开发,引擎开发:支持类 ...
- .NET面试题系列[4] - C# 基础知识(2)
2 类型转换 面试出现频率:主要考察装箱和拆箱.对于有笔试题的场合也可能会考一些基本的类型转换是否合法. 重要程度:10/10 CLR最重要的特性之一就是类型安全性.在运行时,CLR总是知道一个对象是 ...
- 我们公司的ASP.NET 笔试题,你觉得难度如何
本套试题共8个题,主要考察C#面向对象基础,SQL和ASP.NET MVC基础知识. 第1-3题会使用到一个枚举类,其定义如下: public enum QuestionType { Text = , ...
- 我设计的ASP.NET笔试题,你会多少呢
本笔试题考查范围包括面向对象基础.HTML.CSS.JS.EF.jQuery.SQL.编码思想.算法等范围. 第1题:接口和抽象类有何区别? 第2题:静态方法和实例方法有何区别? 第3题:什么是多态? ...
- 猫哥网络编程系列:详解 BAT 面试题
从产品上线前的接口开发和调试,到上线后的 bug 定位.性能优化,网络编程知识贯穿着一个互联网产品的整个生命周期.不论你是前后端的开发岗位,还是 SQA.运维等其他技术岗位,掌握网络编程知识均是岗位的 ...
随机推荐
- 浅识J2EE十三个规范
前言 没有规矩不成方圆,学习J2EE,先来明白都有什么规范. 内容 1.JDBC(Java Database Connectivity)java数据库连接 a)为java开发人员提供了一个行业标准AP ...
- 洛谷P3901 数列找不同(莫队)
传送门 我不管我不管我就是要用莫队 直接用莫队裸上 //minamoto #include<iostream> #include<cstdio> #include<alg ...
- P1505 [国家集训队]旅游
\(\color{#0066ff}{题 目 描 述}\) Ray 乐忠于旅游,这次他来到了T 城.T 城是一个水上城市,一共有 N 个景点,有些景点之间会用一座桥连接.为了方便游客到达每个景点但又为了 ...
- 10.11cdy考试题
鸣谢cdy math [题目描述] 众所周知, xkj是GH的得意门生, 可是xkj的数学成绩并不是很理想: 每次GH在批评完数学限训做的差的人时, 总会在后面加一句:咱们班还有一位做的最差的同学-- ...
- 高仿JDK动态代理 底层源码实现
动态代理实现思路 实现功能:通过Proxy.newProxyInstance返回代理对象 1.创建一个处理业务逻辑的接口,我们也和JDK一样,都使用InvocationHandler作为接口名,然后接 ...
- 2017第八届蓝桥杯决赛(C++ B组)2.磁砖样式
磁砖样式 小明家的一面装饰墙原来是 310 的小方格. 现在手头有一批刚好能盖住2个小方格的长方形瓷砖. 瓷砖只有两种颜色:黄色和橙色. 小明想知道,对于这么简陋的原料,可以贴出多少种不同的花样来. ...
- Tensorflow方法介绍
一.reduce系列函数(维度操作) 1.tf.reduce_sum( input_tensor, axis=None, keep_dims=False, name=None, reduction_i ...
- Qt 学习之路 2(7):MainWindow 简介
Qt 学习之路 2(7):MainWindow 简介 豆子 2012年8月29日 Qt 学习之路 2 29条评论 前面一篇大致介绍了 Qt 各个模块的相关内容,目的是对 Qt 框架有一个高屋建 ...
- 对DeepLung数据预处理部分的详细展示
之前有解释预处理部分的函数,不过觉得还不够详细,同时文字解释还不够直观,所以现在想一步步运行下,打印输出 首先读取原始数据,包括相应的注释(即结节标签)[注意]注释文件中的标签是按x,y,z的顺序给的 ...
- Canvas 动态小球重叠效果
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...