正式學習c#,ASP.NET已經有半年多了,期間一直在忙一個項目,很少有時間能夠看看基礎知識,前兩天看到一貼子,突然發現自己的基礎知識是如此的薄弱,很多問題是“知其然不知其所以然”,基礎知識的缺失註定達不到你所想要的高度。因此,現在想抽出時間來看看基礎。但是自己有不想再去一頁一頁的看那本那麼厚的《c#進階編程》和《ASP.NET2.0進階編程》,所以想到什麼地方就看什麼地方了,不求順序,但求效果。
然,看了書後,自己就想寫點什麼,一是加深理解和印象,二是和園友們共同探討。自己從來沒有寫過系列文章,就從這裡開始吧。雖然都是寫基礎的東西,對高手來說都是再簡單不過了,但對我這樣的新手來說,基礎的才是最重要的。
於是,就寫這個系列:
1、記憶體管理
c#編程的一個優點是程式員不需要關心具體的記憶體管理,尤其是垃圾收集器會處理所有的記憶體清理工作。雖然不必手工管理記憶體,但如果要編寫高品質的代碼,還是要理解後台發生的事情,理解c#的記憶體管理。本文主要介紹給變數分配記憶體時電腦記憶體中發生的情況。
c#將資料分為兩種:值資料類型和引用資料類型,這兩種資料類型儲存在記憶體中的不同的地方:值資料類型儲存在堆棧中,而參考型別儲存在記憶體的託管堆中。
一、記憶體簡介
Windows使用一個系統:虛擬定址系統。這個系統的作用是將程式可用的記憶體位址映射到硬體記憶體中的實際地址上。其實際結果就是32位的機子上每個進程都可以使用4GB的記憶體,當然,64位機這個數字就大了去了。這4GB的記憶體實際上包含了程式的所有的部分:可執行代碼,DLL以及程式運行時使用的所有變數的內容。這個4GB的記憶體成為虛擬位址空間或虛擬記憶體。為方便,這裡成為記憶體。
4GB中的每個儲存單元都是從零開始向上儲存的。要訪問儲存在記憶體中的某個空間中的值,就必須提供表示該儲存單元的一個數字。在進階程式設計語言中,編譯器的一個重要作用就是負責將人們可以理解的變數名稱變為處理器可以理解的記憶體位址。
二、堆棧
在記憶體中,有一個地區成為堆棧,儲存物件
- 對象成員的值資料類型
- 調用方法時,傳遞給所有方法的參數的副本
注意:調用方法時,堆棧儲存的是所有參數的副本,因此,經實值型別A傳遞給函數,A的值是不會變化的。當然,參考型別是會變化的,因為在堆棧中儲存的是參考型別的地址,這在後面會有詳細的介紹。
下面以一個例子來說明堆棧的工作方式,如下面的代碼:
1: {
2: int a;
3: //do something;
4: {
5: int b;
6: //do something
7: }
8: }
首先聲明a,在內部的代碼塊中聲明b。然後內部的代碼塊終止,b就出了範圍,最後a出範圍。所以b的生命週期總是包含在a的生命週期內,在釋放變數的時候,其順序總是和分配記憶體的順序是相反的。即:變數的生存周期都是嵌套的。這就是堆棧的工作方式。
三、託管堆
堆棧具有相當高的效能,但是變數的生命週期必須是嵌套的,這個要求在有的時候過於苛刻。我們希望有一種別的方法來分配記憶體,儲存一些資料,並在方法退出的很長一段時間內,這些資料仍然是可用的,這時,就使用託管堆。
託管堆(簡稱堆)是記憶體中的另外一個地區,我們仍然用一個例子來說明堆的工作方式,如下面代碼:
1: {
2: Customer customer1;
3: customer1=new Customer();
4: Customer customer2=new Customer();
5: //do something
6: }
首先,聲明一個Customer:customer1。在堆棧上給這個引用分配儲存控制項。請注意:僅僅是給這個引用分配儲存空間,並不是實際的Customer對象。customer1佔用4個位元組的空間(32位機),來表示Customer對象在記憶體中的地址。
然後,執行第二行代碼,完成以下操作:
- 在堆上分配儲存空間,用來儲存Customer對象,注意:這裡是Customer對像。
- 將變數customer1的值設為分配給Customer對象的記憶體位址
從這個例子中可以看出,建立參考型別的變數的過程要比獎勵實值型別變數的過程複雜,且不避免的有效能的降低。但是,我們可以將一個引用變數的值賦給另一個引用變數,當一個變數出範圍時,它會從堆棧中刪除,但是對象的資料仍然保留在記憶體中,知道程式停止。
這樣,我們在將一個引用變數A傳遞給函數時,僅僅是將變數A的引用傳遞給了函數,即:僅僅是在堆棧上分配記憶體,即變數B。兩者指向同一個記憶體位址。因此,當變數B發生變化時,變數A也會發生變化。
四、裝箱和拆箱
裝箱和拆箱就是實值型別和參考型別的項目轉化,裝箱可以將實值型別轉化為參考型別,拆箱的作用正好相反,經參考型別轉化為實值型別。
五、垃圾收集
一般情況下,.NET運行庫會在認為需要的時候運行垃圾收集器來釋放託管資源,這在大多數情況下,足夠了。就是說我們沒有必要去關心記憶體。但在有的情況下,我們會強制記憶體回收集器在代碼的某個地方運行,釋放記憶體。這就用到了System.GC.Collect()。System.GC表示一個垃圾收集器。這種情況很少,例如:代碼中大量的對象剛剛停止引用,就適合調用垃圾收集器。
六、總結
c#將記憶體分為兩個地區:堆棧和堆。堆棧存放實值型別,堆存放參考型別。.NET運行庫會在認為需要的時候運行垃圾收集器來釋放記憶體。