.NET中的六個重要概念:棧、堆、值類型、引用類型、裝箱和拆箱

內容導讀

  • 概述
  • 當你聲明一個變量背後發生了什麼?
  • 堆和棧
  • 值類型和引用類型
  • 哪些是值類型,哪些是引用類型?
  • 裝箱和拆箱
  • 裝箱和拆箱的性能問題

1、概述

本文會闡述六個重要的概念:堆、棧、值類型、引用類型、裝箱和拆箱。本文首先會經過闡述當你定義一個變量以後系統內部發生的改變開始講解,而後將關注點轉移到存儲雙雄:堆和棧。以後,咱們會探討一下值類型和引用類型,並對有關於這兩種類型的重要基礎內容作一個講解。編程

本文會經過一個簡單的代碼來展現在裝箱和拆箱過程當中所帶來的性能上的影響,請各位仔細閱讀。函數

01

2、當你聲明一個變量背後發生了什麼?

當你在一個.NET應用程序中定義一個變量時,在RAM中會爲其分配一些內存塊。這塊內存有三樣東西:變量的名稱變量的數據類型以及變量的值工具

上面簡單闡述了內存中發生的事情,可是你的變量究竟會被分配到哪一種類型的內存取決於數據類型。在.NET中有兩種可分配的內存:。在接下來的幾個部分中,咱們會試着詳細地來理解這兩種類型的存儲。性能

02

3、存儲雙雄:堆和棧

爲了理解棧和堆,讓咱們經過如下的代碼來了解背後到底發生了什麼。測試

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

  • Line 1:當這一行被執行後,編譯器會在棧上分配一小塊內存。棧會在負責跟蹤你的應用程序中是否有運行內存須要
  • Line 2:如今將會執行第二步。正如棧的名字同樣,它會將此處的一小塊內存分配疊加在剛剛第一步的內存分配的頂部。你能夠認爲棧就是一個一個疊加起來的房間或盒子。在棧中,數據的分配和解除都會經過LIFO (Last In First Out)即先進後出的邏輯規則進行。換句話說,也就是最早進入棧中的數據項有可能最後纔會出棧。
  • Line 3:在第三行中,咱們建立了一個對象。當這一行被執行後,.NET會在棧中建立一個指針,而實際的對象將會存儲到一個叫作「堆」的內存區域中。「堆」不會監測運行內存,它只是可以被隨時訪問到的一堆對象而已。不一樣於棧,堆用於動態內存的分配。
  • 這裏須要注意的另外一個重要的點是對象的引用指針是分配在棧上的。 例如:聲明語句 Class1 cls1; 其實並無爲Class1的實例分配內存,它只是在棧上爲變量cls1建立了一個引用指針(而且將其默認職位null)。只有當其遇到new關鍵字時,它纔會在堆上爲對象分配內存。
  • 離開這個Method1方法時(the fun):如今執行控制語句開始離開方法體,這時全部在棧上爲變量所分配的內存空間都會被清除。換句話說,在上面的示例中全部與int類型相關的變量將會按照「LIFO」後進先出的方式從棧中一個一個地出棧。
  • 須要注意的是:這時它並不會釋放堆中的內存塊,堆中的內存塊將會由垃圾回收器稍候進行清理。

03

 

如今咱們許多的開發者朋友必定很好奇爲何會有兩種不一樣類型的存儲?咱們爲何不能將全部的內存塊分配只到一種類型的存儲上?pwa

若是你觀察足夠仔細,基元數據類型並不複雜,他們僅僅保存像 ‘int i = 0’這樣的值。對象數據類型就複雜了,他們引用其餘對象或其餘基元數據類型。換句話說,他們保存其餘多個值的引用而且這些值必須一一地存儲在內存中。對象類型須要的是動態內存而基元類型須要靜態內存。若是需求是動態內存的話,那麼它將會在堆上爲其分配內存,相反,則會在棧上爲其分配。3d

04

4、值類型和引用類型

既然咱們已經瞭解了棧和堆的概念了,是時候瞭解值類型和引用類型的概念了。值類型將數據和內存都保存在同一位置,而一個引用類型則會有一個指向實際內存區域的指針。指針

經過下圖,咱們能夠看到一個名爲i的整形數據類型,它的值被賦值到另外一個名爲j的整形數據類型。他們的值都被存儲到了棧上。code

當咱們將一個int類型的值賦值到另外一個int類型的值時,它其實是建立了一個徹底不一樣副本。換句話說,若是你改變了其中某一個的值,另外一個不會發生改變。因而,這些種類的數據類型被稱爲「值類型」。

05

當咱們建立一個對象而且將此對象賦值給另一個對象時,他們彼此都指向了以下圖代碼段所示的內存中同一塊區域。所以,當咱們將obj賦值給obj1時,他們都指向了堆中的同一塊區域。換句話說,若是此時咱們改變了其中任何一個,另外一個都會受到影響,這也說明了他們爲什麼被稱爲「引用類型」。

5、哪些是值類型,哪些是引用類型?

在.NET中,變量是存儲到棧仍是堆中徹底取決於其所屬的數據類型。好比:‘String’或‘Object’屬於引用類型,而其餘.NET基元數據類型則會被分配到棧上。下圖則詳細地展現了在.NET預置類型中,哪些是值類型,哪些又是引用類型。

06

6、裝箱和拆箱

如今,你已經有了很多的理論基礎了。如今,是時候瞭解上面的知識在實際編程中的使用了。在應用中最大的一個意義就在於:理解數據從棧移動到堆的過程當中所發生的性能消耗問題,反之亦然。

考慮一下如下的代碼片斷,當咱們將一個值類型轉換爲引用類型,數據將會從棧移動到堆中。相反,當咱們將一個引用類型轉換爲值類型時,數據也會從堆移動到棧中。

不論是在從棧移動到堆仍是從堆中移動到棧上都會不可避免地對系統性能產生一些影響。

因而,兩個新名詞橫空出世:當數據從值類型轉換爲引用類型的過程被稱爲「裝箱」,而從引用類型轉換爲值類型的過程則被成爲「拆箱」。

07

若是你編譯一下上面這段代碼而且在ILDASM(一個IL的反編譯工具)中對其進行查看,你會發如今IL代碼中,裝箱和拆箱是什麼樣子的。下圖則展現了示例代碼被編譯後所產生的IL代碼。

08

7、裝箱和拆箱的性能問題

爲了弄明白到底裝箱和拆箱會帶來怎樣的性能影響,咱們分別循環運行10000次下圖所示的兩個函數方法。其中第一個方法中有裝箱操做,另外一個則沒有。咱們使用一個Stopwatch對象來監視時間的消耗。

具備裝箱操做的方法花費了3542毫秒來執行完成,而沒有裝箱操做的方法只花費了2477毫秒,整整相差了1秒多。並且,這個值也會由於循環次數的增長而增長。也就是說,咱們要儘可能避免裝箱和拆箱操做。在一個項目中,若是你須要裝箱和裝箱,請仔細考慮它是不是絕對必不可少的操做,若是不是,那麼儘可能不用。

09

雖然以上代碼段沒有展現拆箱操做,但其效果一樣適用於拆箱。你能夠經過寫代碼來實現拆箱,而且經過Stopwatch來測試其時間消耗。

 

原文:http://blog.jobbole.com/77946/

相關文章
相關標籤/搜索