程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> .NET中的六個重要概念:棧、堆、值類型、引用類型、裝箱和拆箱,.net裝箱

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

編輯:C#入門知識

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


 內容導讀

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

 一、概述

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

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

1

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

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

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

2

 三、存儲雙雄:堆和棧

  為了理解棧和堆,讓我們通過以下的代碼來了解背後到底發生了什麼。

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(); }

  代碼只有三行,現在我們可以一行一行地來了解到底內部是怎麼來執行的。

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

3

  現在我們許多的開發者朋友一定很好奇為什麼會有兩種不同類型的存儲?我們為什麼不能將所有的內存塊分配只到一種類型的存儲上?

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

4

 四、值類型和引用類型

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

  通過下圖,我們可以看到一個名為i的整形數據類型,它的值被賦值到另一個名為j的整形數據類型。他們的值都被存儲到了棧上。

  當我們將一個int類型的值賦值到另一個int類型的值時,它實際上是創建了一個完全不同的副本。換句話說,如果你改變了其中某一個的值,另一個不會發生改變。於是,這些種類的數據類型被稱為“值類型”。

5

  當我們創建一個對象並且將此對象賦值給另外一個對象時,他們彼此都指向了如下圖代碼段所示的內存中同一塊區域。因此,當我們將obj賦值給obj1時,他們都指向了堆中的同一塊區域。換句話說,如果此時我們改變了其中任何一個,另一個都會受到影響,這也說明了他們為何被稱為“引用類型”。

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

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

6

 六、裝箱和拆箱

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

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

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

  於是,兩個新名詞橫空出世:當數據從值類型轉換為引用類型的過程被稱為“裝箱”,而從引用類型轉換為值類型的過程則被成為“拆箱”。

7

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

8

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

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

  具有裝箱操作的方法花費了3542毫秒來執行完成,而沒有裝箱操作的方法只花費了2477毫秒,整整相差了1秒多。而且,這個值也會因為循環次數的增加而增加。也就是說,我們要盡量避免裝箱和拆箱操作。在一個項目中,如果你需要裝箱和裝箱,請仔細考慮它是否是絕對必不可少的操作,如果不是,那麼盡量不用。

10

  雖然以上代碼段沒有展示拆箱操作,但其效果同樣適用於拆箱。你可以通過寫代碼來實現拆箱,並且通過Stopwatch來測試其時間消耗。

  原文出處: Shivprasad koirala

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved