詳解.NET中六個你必須知道的重要概念
原創【51CTO獨家特稿】我也制作了很多視頻教程,包括設計模式,WCF,WWF,WPF,LINQ,Silverlight,UML,Sharepoint,Azure,VSTS等,可去http://tinyurl.com/mra3hx觀看。
圖 1 Oh,My Lady gaga,這些東西好復雜哦
當你聲明一個變量時內存中都發生了什么?
當你在一個.Net應用程序中聲明一個變量時,首先要分配一些內存快到RAM,它包括三樣東西,第一個是變量名,第二個是變量的數據類型,最后一個是變量的值。
這只是一個很簡單的解釋,根據變量的數據類型不同,有兩種內存分配類型:堆棧內存和堆內存。
圖 2 聲明變量后的內存結構
堆棧(stack)和堆(heap)
為了幫助理解堆棧和堆,讓我們了解下面的代碼內部究竟發生了什么。
- public void Method1()
- {
- // Line 1
- int i=4;
- // Line 2
- int y=2;
- //Line 3
- class1 cls1 = new class1();
- }
這個方法內部只有三行代碼,下面我就逐行解釋內部發生了什么事情。
第一行:執行該行時,編譯器分配一小塊叫做堆棧的內存,堆棧負責保持跟蹤應用程序運行需要的內存。
第二行:現在執行移動到下一步,正如堆棧的名稱所暗示的那樣,這個內存分配時疊放在前一個內存分配頂部的,你可以將堆棧理解為一系列隔間或盒子的逐層堆積。
內存分配和解除分配使用LIFO(Last in first out,后進先出)邏輯,換句話說就是內存是在內存的末尾(如堆棧的頂部)分配和解除分配的。
第三行:在第三行我們創建了一個對象,執行該行時,它在堆棧上創建一個指針,真實的對象是存儲一個不同類型的內存分配(叫做堆)中,堆不會跟蹤運行的內存,它只是對象的堆積,堆用于動態內存分配。
退出方法(有趣):執行完最后一行代碼后就該退出這個方法了,當它傳遞結束控制時,它就會清除分配到堆棧上的所有內存變量,換句話說就是所有與int數據類型關聯的變量按照LIFO方式從堆棧中解除分配。
但不會解除堆內存分配,這部分內存要通過GARBAGE COLLECTOR(垃圾回收器)解除分配。
圖 3 三行代碼對應的內存內部操作
很多人現在可能要問為什么要設置兩種內存分配形式呢?難道就不能用一種內存分配形式完成內存分配嗎?
如果你仔細觀察上圖,你就會知道int變量是分配在堆棧上的,因為編譯器已經知道它們可以存儲多少數據(-2,147,483,648到2,147,483,647),涉及到對象時,編譯器不知道需要多少內部空間,因此在堆上分配相同大小的空間。
換句話說就是,如果不知道數據大小或是動態變化的,就需要分配到堆上,如果數據大小是確定的,就分配到堆棧上。
圖 4 知道變量大小時分配到堆棧上,不知道變量大小時分配到堆上
值類型和引用類型
值類型指的是在相同的位置同時容納數據和內存的類型,而引用類型是借助一個指針指向內存位置。下面是一個簡單的命名為i的整數數據類型,其值是由另一個命名為j的整數數據類型賦予的,這兩個內存值都是基于堆棧分配的。
當我們將一個int值賦給另一個int值時,它創建一個完全不同的拷貝,換句話說就是,你修改其中一個值不會引起另一個值也發生變化,這種數據類型就叫做值類型。
圖 5 值類型:一個值的變化不會引起另一個值變化
當我們將一個對象賦值給另一個對象時,它們指向相同的內存位置,如下圖所示,當我們將obj賦值給obj1時,它們指向的內存位置是一樣的。換句話說就是,如果我們修改了其中一個對象,另一個對象也會受到影響,這種類型就叫做引用類型。
圖 6 引用類型:一個對象的變化會引起另一個對象的變化
哪一個數據類型是值類型和引用類型呢?
在.Net中,根據數據類型不同,變量可能是基于堆棧分配的,也可能是基于堆分配的,String和Objects是引用類型,其它.Net數據類型是基于堆棧分配的,下圖更詳細地進行了解釋。
圖 7 引用類型和值類型對應.Net中的數據類型
#p#
裝箱(boxing)和拆箱(unboxing)
說了這么多,在實際編程時怎么使用它們呢?最大的問題是要弄清楚數據從堆棧移到堆的性能損失,反之亦然。
如下圖所示,當我們將一個值類型移到引用類型時,數據也從堆棧移到堆中,當我們將引用類型移到值類型時,數據就從堆移到堆棧中。數據從堆棧移到堆,或是從堆移到堆棧,都會產生較大的性能損失。數據從值類型移到引用類型的過程叫做裝箱,從引用類型移到值類型叫做拆箱。
圖 8 裝箱和拆箱過程示意
如果你編譯上面的代碼,在相同的ILDASM中,你會看到在IL中的代碼是如何裝箱和拆箱的,如下圖所示。
圖 9 裝箱和拆箱
裝箱和拆箱的性能影響
為了查看性能的影響,我們將下面兩個函數運行了1000次,如下圖所示,左邊的函數有裝箱拆箱操作,右邊的函數沒有,我們使用了一個秒表對象監控所花的時間。
圖 10 有裝箱拆箱和無裝箱拆箱執行時間對比
從上圖我們看到,有裝修拆箱需要花3542毫秒,無裝修拆箱只需2477毫秒,因此對性能的影響還是蠻大的。
現在你對這兩個重要的.Net概念是否都理解了呢?
原文名:6 important .NET concepts: - Stack, heap, Value types, reference types, boxing and Unboxing.
【編輯推薦】