自整理面试题汇总
-
值类型和引用类型的区别
值类型一般存在栈上,引用类型存在堆上。 -
struct 里面如果包含一个类的对象,该对象是存在堆区和栈区?
类对象依然在堆中,结构体保存在栈里,持有类对象的引用。 -
string和stringbuilder的区别?
c#对string类型做了优化,string类型通过存在栈上,下一次再次创建字符串时,就会查找字符串池(散列表)中是否存在,存在直接指向之前的地址,这就是为什么c#内容相同的两个string使用 “==”,“Equals”,“ReferenceEquals” 都能返回true的原因。但该方式在频繁对string进行增删操作时,会造成大量的内存浪费,大量的gc。因为每次增删都会重新申请内存,在栈上创建一个新的string对象。为了优化该情况,c#也有stringBuilder,StringBuilder保存在堆上,在一开始声明好长度后,若后续超出声明的长度,则长度倍增。也就是在没达到容量上限之前,做增删操作是不会重新分配新的内存空间了,也就不会造成浪费。 -
string +和string.format拼接字符串会浪费内存么?
string+会频繁的创建新的对象,就会频繁地申请内存,但若写在一句中,即(str1+str2+str3+str4)实际上并不是每连接一个都分配一次内存,而是把几个字符都作为 String.Concat 方法的参数,只分配一次内存,如:
string s = s1 + s2 + s3 + s4 + s5;
最终会被编译为:string.Concat(s1, s2, s3, s4, s5);
而String.Format是在StringBuilder基础上的封装,在数据拼接大的情况下,不如直接用StringBuilder来得方便。 -
字符串池有什么用,原理是什么?
字符串池也叫字符串驻留池,是.Net CLR默默维护的一个hash表,其中Key是字符串内容,Value是该字符串在堆上的地址。这个表记录了所有在代码中使用字面量声明的字符串实例的引用,是CLR防止字符串冗余的一种优化机制。当然,不适用字面量声明的字符串是不会检测驻留池的,如new出来的字符串,但可以通过Intern方法实现检测
如 string s2 = String.Intern(sb.ToString()); 就会强制检测驻留池是否存在该字符串。 -
数组和链表的区别?
- 数组是内存连续的一种数据结构,通过索引查找较快,但增删比较慢,在数组中间进行增删则会导致后续一段内存需要移位重写。
- 链表是一种查找较慢但增删更快更容易的数据结构,单向链表每个节点保持下一个节点的引用,增删只需要改引用地址即可,但查找需要从表头一个一个找起。
-
字典的实现原理?字典为什么查找快?
- 字典内部由一个int类型的数组Buckets和一个结构体数组Entries组成,该结构体Entry内部包含Key和Value以及一个int类型的Next和int类型的HashCode,字典添加元素时,将Key进行一次Hash,得到的结果放在对应index的Buckets里,而该Buckets槽中存储当前Hash结果指向的Entries槽,并放在和结果对应下标的Entries中,若是发生Hash碰撞,则Buckets槽中的数据重新指向新的槽,但槽内的结构体的Next指向发生碰撞的前一个元素的下标,形成一个链表。
- 因为理想的字典是1对1的关系,可以通过Key和Hash算法还有Buckets(Hash桶)快速定位下标来查找元素。但如果发生碰撞过多,最糟糕的情况所有元素Hash结果一样,那么查找的时间复杂度也就变成了O(n)
-
hash的原理是什么?
空间换时间,hash函数是一个映射,将关键字集合映射到一个地址集合上,hash表是通过一个无序数组,键则是地址集合,代表索引,值则是索引对应的值。 -
hash查找的时间复杂度是多少?hash查找冲突的情况如何处理?
理想情况下为O(1) 最糟糕情况则是链表结构,为O(n)。- 开放定址法(再散列法) 再冲突时,将hash结果通过一个通用的再散列函数进行进一步的散列,直到不再冲突为止。
- 再哈希法 和再散列法不同,不再使用一个通用的再散列函数,而是构造多个不同的hash函数,碰撞后按顺序调用。
- 链地址法 将同样hash结果的元素,构成一个单向链表结构。