这个在面试中被问到的概率还是蛮高的,也有一些引申的问题,比如类和结构体的区别,所以将自己总结的知识点记录一下。
值类型就相当于将有一份电子文档,你的朋友也想要看,于是你将它发送给了你的朋友,朋友那边相当于拿到了你的复印本,但是在他那边的修改与你这边无关,不会进行同步。
而引用类型相当于你们一起看电视,使用遥控器对电视机进行操控,无论是你们谁进行操作都会直接影响到电视机的显示。
用图说明的话大概如下所示:
t1和t2都是引用类型,他们的值都是指向同一个对象的引用,而不是自己本身,且引用类型储存在堆上。
String类型是一个引用类型而非值类型,它并非是一个实际的字符串,而是对字符串的一个引用,要特别注意。
而值类型则是一一对应储存,储存在栈上,但也会有特殊情况,例如数组中的元素,引用类型中的值类型字段、迭代器块中的局部变量、闭包情况下匿名函数的局部变量,在这几种情况下值类型实例如果分配在线程栈上,有可能会出现线程栈中的方法已经调用结束,但是还会访问这些值得情况,于是他们也被分配在了托管堆上,以满足在方法返回之后还能被访问的需求。
引用类型和值类型另一个显著的区别就在于值类型不能派生出其他类型,也不能作为其他类型的基类型,它是隐式密封的,因此值类型无需提供额外信息去表示它的实际类型,因此他没有引用类型中有的额外的信息。也可以说值类型的使用缓解了托管堆的压力,并且减少了消耗巨大的垃圾回收的次数。
垃圾回收也被称为GC,它是系统垃圾回收的一种机制,在进程开始的时候,系统会开辟一块固定大小的内存堆,当这块内存堆达到储存上限时,就会寻找不常用的引用类型将他从堆上清除,然后加入新的引用类型。
因此,GC的优化应该是减少引用类型的生成,比如说string字符串的相加,会额外生成引用类型,我们可以使用StringBuilder;以及尽量使用Compare.Tag而不是tag=””,这样会生成一个新的字符串;以及减少foreach的使用,他一次循环就会生成多次垃圾;以及使用对象池。
装箱和拆箱也会引发GC,同时它们也和值类型引用类型有关,简单来说,就是值类型和引用类型之间的转换。
例如生成一个值类型:int i = 1;
将它加入Arraylist:ArrayList.Add(i);
这就是一个装箱操作,因为Add方法的类型是Object的,而i是值类型,为了是代码能够正常运行,值类型的实例在这里必须转换成真正分配在内存堆上的对象,且必须获得对该对象的引用。
而拆箱就比如将ArrayList里的值赋值给值类型:int j = Arraylist[0];
相比于装箱,拆箱的消耗要小很多,因为拆箱其实就是获取引用的过程,获取的这个引用指向了一个分配在托管堆上的对象中的值。且拆箱时,一定只能转化成最初未装箱的值类型,如上述例子j就只能是int而不能是long。
因此装箱也会引发GC,注重性能优化的话就避免使用ArrayList,可以使用泛型List<T>。