本文会阐述六个重要的概念:堆、栈、值类型、引用类型、装箱和拆箱。本文首先会经过阐述当你定义一个变量以后系统内部发生的改变开始讲解,而后将关注点转移到存储双雄:堆和栈。以后,咱们会探讨一下值类型和引用类型,并对有关于这两种类型的重要基础内容作一个讲解。编程
本文会经过一个简单的代码来展现在装箱和拆箱过程当中所带来的性能上的影响,请各位仔细阅读。函数
当你在一个.NET应用程序中定义一个变量时,在RAM中会为其分配一些内存块。这块内存有三样东西:变量的名称、变量的数据类型以及变量的值。工具
上面简单阐述了内存中发生的事情,可是你的变量究竟会被分配到哪一种类型的内存取决于数据类型。在.NET中有两种可分配的内存:栈和堆。在接下来的几个部分中,咱们会试着详细地来理解这两种类型的存储。性能
为了理解栈和堆,让咱们经过如下的代码来了解背后到底发生了什么。测试
1
2
3
4
5
6
7
8
9
10
11
|
public
void
Method1()
{
// Line 1
int
i=
4
;
// Line 2
int
y=
2
;
//Line 3
class1 cls1 =
new
class1();
}
|
代码只有三行,如今咱们能够一行一行地来了解到底内部是怎么来执行的。spa
如今咱们许多的开发者朋友必定很好奇为何会有两种不一样类型的存储?咱们为何不能将全部的内存块分配只到一种类型的存储上?pwa
若是你观察足够仔细,基元数据类型并不复杂,他们仅仅保存像 ‘int i = 0
’这样的值。对象数据类型就复杂了,他们引用其余对象或其余基元数据类型。换句话说,他们保存其余多个值的引用而且这些值必须一一地存储在内存中。对象类型须要的是动态内存而基元类型须要静态内存。若是需求是动态内存的话,那么它将会在堆上为其分配内存,相反,则会在栈上为其分配。3d
既然咱们已经了解了栈和堆的概念了,是时候了解值类型和引用类型的概念了。值类型将数据和内存都保存在同一位置,而一个引用类型则会有一个指向实际内存区域的指针。指针
经过下图,咱们能够看到一个名为i的整形数据类型,它的值被赋值到另外一个名为j的整形数据类型。他们的值都被存储到了栈上。code
当咱们将一个int类型的值赋值到另外一个int类型的值时,它其实是建立了一个彻底不一样的副本。换句话说,若是你改变了其中某一个的值,另外一个不会发生改变。因而,这些种类的数据类型被称为“值类型”。
当咱们建立一个对象而且将此对象赋值给另一个对象时,他们彼此都指向了以下图代码段所示的内存中同一块区域。所以,当咱们将obj赋值给obj1时,他们都指向了堆中的同一块区域。换句话说,若是此时咱们改变了其中任何一个,另外一个都会受到影响,这也说明了他们为什么被称为“引用类型”。
在.NET中,变量是存储到栈仍是堆中彻底取决于其所属的数据类型。好比:‘String’或‘Object’属于引用类型,而其余.NET基元数据类型则会被分配到栈上。下图则详细地展现了在.NET预置类型中,哪些是值类型,哪些又是引用类型。
如今,你已经有了很多的理论基础了。如今,是时候了解上面的知识在实际编程中的使用了。在应用中最大的一个意义就在于:理解数据从栈移动到堆的过程当中所发生的性能消耗问题,反之亦然。
考虑一下如下的代码片断,当咱们将一个值类型转换为引用类型,数据将会从栈移动到堆中。相反,当咱们将一个引用类型转换为值类型时,数据也会从堆移动到栈中。
不论是在从栈移动到堆仍是从堆中移动到栈上都会不可避免地对系统性能产生一些影响。
因而,两个新名词横空出世:当数据从值类型转换为引用类型的过程被称为“装箱”,而从引用类型转换为值类型的过程则被成为“拆箱”。
若是你编译一下上面这段代码而且在ILDASM(一个IL的反编译工具)中对其进行查看,你会发如今IL代码中,装箱和拆箱是什么样子的。下图则展现了示例代码被编译后所产生的IL代码。
为了弄明白到底装箱和拆箱会带来怎样的性能影响,咱们分别循环运行10000次下图所示的两个函数方法。其中第一个方法中有装箱操做,另外一个则没有。咱们使用一个Stopwatch对象来监视时间的消耗。
具备装箱操做的方法花费了3542毫秒来执行完成,而没有装箱操做的方法只花费了2477毫秒,整整相差了1秒多。并且,这个值也会由于循环次数的增长而增长。也就是说,咱们要尽可能避免装箱和拆箱操做。在一个项目中,若是你须要装箱和装箱,请仔细考虑它是不是绝对必不可少的操做,若是不是,那么尽可能不用。
虽然以上代码段没有展现拆箱操做,但其效果一样适用于拆箱。你能够经过写代码来实现拆箱,而且经过Stopwatch来测试其时间消耗。
原文:http://blog.jobbole.com/77946/