什么是棧堆
在計算機領域,堆棧是一個不容忽視的概念,棧堆是兩種數據結構。堆棧都是一種數據項按序排列的數據結構,只能在一端(稱為棧頂(top))對數據項進行插入和刪除。要點:堆,隊列優先,先進先出(FIFO—first in first out);棧,先進后出(FILO—First-In/Last-Out)。
堆棧是一個在計算機科學中經常使用的抽象數據類型。堆棧中的物體具有一個特性: 最后一個放入堆棧中的物體總是被最先拿出來, 這個特性通常稱為后進先出(LIFO)隊列。
棧和堆的區別
堆棧空間分配
棧(操作系統):由操作系統自動分配釋放,存放函數的變量值,局部變量的值等等,其操作方式類似於數據結構中的棧;
堆(操作系統):一般由開發者分配釋放,若不釋放,程序結束時可能會有OS回收,分配方式倒是類似於鏈表;
堆棧的緩存方式
棧使用的是一級緩存,通常是被調用時處於儲存空間,調用完后自動釋放;
堆使用的是二級緩存,生命周期有虛擬機的垃圾回收算法來決定(並不一定成為孤兒對象就被立即釋放)。所以調用這些對象的速度相對來得慢一些;
堆棧數據結構的區別
堆(數據結構):堆可以看做是一棵樹;例如:堆排序;
棧(數據結構):一種先進后出的數據結構;
區別介紹
棧負責保存我們的代碼執行(或調用)路,而堆則負責保存對象(或者說數據)的路徑。
可以將棧想象成一堆從頂向下堆疊的盒子。當每調用一次方法時,我們將應用程序中所要發生的事情記錄在棧頂的一個盒子中,而我們每次只能夠使用棧頂的那個盒子。當我們棧頂的盒子被使用完之后,或者說方法執行完畢之后,我們將拋開這個盒子然后繼續使用棧頂上的新盒子。堆的工作原理比較相似,但大多數時候堆用作保存信息而非保存執行路徑,因此堆能夠在任意時間被訪問。與棧相比堆沒有任何訪問限制,堆就像床上的舊衣服,我們並沒有花時間去整理,那是因為可以隨時找到一件我們需要的衣服,而棧就像儲物櫃里堆疊的鞋盒,我們只能從最頂層的盒子開始取,直到發現那只合適的。
以上圖片並不是內存中真實的表現形式,但能夠幫助我們區分棧和堆。
棧是自行維護的,也就是說內存自動維護棧,當棧頂的盒子不再被使用,它將被拋出。相反的,堆需要考慮垃圾回收,垃圾回收用於保持堆的整潔性,沒有人願意看到周圍都是贓衣服,那簡直太臭了!
當我們的代碼執行的時候,棧和堆中主要放置了四種類型的數據:值類型(Value Type),引用類型(Reference Type),指針(Pointer),指令(Instruction)。
1.值類型
bool,byte ,char,decimal,double,enum,float,int,long,sbyte,short,struct,uint,ulong,ushort
2.引用類型
class,interface,delegate ,object ,string
3.指針
在內存管理方案中放置的第三種類型是類型引用,引用通常就是一個指針。我們不會顯示的使用指針,它們由公共語言運行時(CLR)來管理。指針(或引用)是不同於引用類型的,是因為當我們說某個事物是一個引用類型時就意味着我們是通過指針來訪問它的。指針是一塊內存空間,而它指向另一個內存空間。就像棧和堆一樣,指針也同樣要占用內存空間,但它的值是一個內存地址或者為空。
如上圖,可以幫我們更加好理解堆和棧,在裝箱和拆箱中也能體現
堆棧使用情況
1. 引用類型總是放在堆中。
2.值類型和指針總是放在它們被聲明的地方。
就像我們先前提到的,棧是負責保存我們的代碼執行(或調用)時的路徑。當我們的代碼開始調用一個方法時,將放置一段編碼指令(在方法中)到棧上,緊接着放置方法的參數,然后代碼執行到方法中的被“壓棧”至棧頂的變量位置。通過以下例子很容易理解...
定義一個方法
public int AddFive(int pValue) { int result; result = pValue + 5; return result; }
現在就來看看在棧頂發生了些什么,記住我們所觀察的棧頂下實際已經壓入了許多別的內容。
首先方法(只包含需要執行的邏輯字節,即執行該方法的指令,而非方法體內的數據)入棧,緊接着是方法的參數入棧。
接着,控制(即執行方法的線程)被傳遞到堆棧中AddFive()的指令上
當方法執行時,我們需要在棧上為“result”變量分配一些內存
方法執行完成,然后方法的結果被返回。
通過將棧指針指向AddFive()方法曾使用的可用的內存地址,所有在棧上的該方法所使用內存都被清空,且程序將自動回到棧上最初的方法調用的位置。
在這個例子中,我們的"result"變量是被放置在棧上的,事實上,當值類型數據在方法體中被聲明時,它們都是被放置在棧上的。
值類型數據有時也被放置在堆上。記住這條規則--值類型總是放在它們被聲明的地方。好的,如果一個值類型數據在方法體外被聲明,且存在於一個引用類型中,那么它將被堆中的引用類型所取代。
來看另一個例子:
//假如我們有這樣一個MyInt類(它是引用類型因為它是一個類類型): public class MyInt { public int MyValue; } //然后執行下面的方法: public MyInt AddFive(int pValue) { MyInt result = new MyInt(); result.MyValue = pValue + 5; return result; }
就像前面提到的,方法及方法的參數被放置到棧上,接下來,控制被傳遞到堆棧中AddFive()的指令上。
接着會出現一些有趣的現象...
因為"MyInt"是一個引用類型,它將被放置在堆上,同時在棧上生成一個指向這個堆的指針引用。
在AddFive()方法被執行之后,我們將清空...
我們將剩下孤獨的MyInt對象在堆中(棧中將不會存在任何指向MyInt對象的指針!)
這就是垃圾回收器(后簡稱GC)起作用的地方。當我們的程序達到了一個特定的內存閥值,我們需要更多的堆空間的時候,GC開始起作用。GC將停止所有正在運行的線程,找出在堆中存在的所有不再被主程序訪問的對象,並刪除它們。然后GC會重新組織堆中所有剩下的對象來節省空間,並調整棧和堆中所有與這些對象相關的指針。你肯定會想到這個過程非常耗費性能,所以這時你就會知道為什么我們需要如此重視棧和堆里有些什么,特別是在需要編寫高性能的代碼時。
當我們使用引用類型時,我們實際是在處理該類型的指針,而非該類型本身。當我們使用值類型時,我們是在使用值類型本身。
案例:
執行以下方法
public int ReturnValue() { int x = new int(); x = 3; int y = new int(); y = x; y = 4; return x; }
最后結果為3
假如我們首先使用MyInt類
public class MyInt { public int MyValue; } //接着執行以下的方法: public int ReturnValue2() { MyInt x = new MyInt(); x.MyValue = 3; MyInt y = new MyInt(); y = x; y.MyValue = 4; //此時操作的是堆 return x.MyValue; }
最后結果為4
為什么?... x.MyValue怎么會變成4了呢?... 看看我們所做的然后就知道是怎么回事了:
在第一例子中,一切都像計划的那樣進行着:
在第二個例子中,我們沒有得到"3"是因為變量"x"和"y"都同時指向了堆中相同的對象。