基礎才是重中之重~理解內存中的棧和堆
.NET中使用stack(棧)和heap(堆)兩種結構在內存中存儲數據,今天咱們就來說說這兩個結構
- Value Types,值類型
在C#中,值類型繼承自System.ValueType的,它們分別是
Bool, byte , char, decimal, double, enu, float, int, long, sbyte, short, struct, uint, ulong, ushort - Reference Types 引用類型
引用類型包括所有的從System.Object繼承下來的類型,它們分別是
class, interface, delegate, object,string,其中string是一種特殊的引用類型,之后的文章中我會單獨說它,.net中的gc(垃圾回收)主要是回收heap中的內存。 - 指針:在C#中它已經被遺忘,但不能不說,因為引用類型在內在中事實上是以指針的形式存儲在Stack上的,而它的數據內容是在Heap上,也就是上引用類型其實是在stack上做了一個地址的引用。
Stack是自我維護的,意味著基本上不需要我們手動進行內存管理,它有自己的內存管理體系,對于window來說最高是1M,而.net最大只能用256K沒有記錯的話,如果超出這個范圍就會出現溢出。 而heap則是我們建立object對象所存儲的地方,它可以由我們自己用GC去回收也可以由.net自己去回收。
對于在教科書上的一個例子,我要說一下,就是斐波那切數列問題,一般書上都是用遞歸寫的,這對于程序員的影響事實上是很大的,如果程序員用普通的遞歸寫這個算法,那你的服務沒準什么時候就掛了,原因是內存出現stack溢出的現象,具體的原因用“老趙”同志寫了段話非常有代表性:它把普通遞歸改寫成了尾遞歸解決了這個問題。
int FactorialTailRecursion(int n, int acc)
{
if (n == 0) return acc;
return FactorialTailRecursion(n - 1, acc * n); //變量仍然對程序有影響,所以編譯器會一次一次堆累它使用的內存
}
改成尾遞歸之后:
int FactorialLoopOptimized(int n, int acc)
{
while (true)
{
if (n == 0) return acc;
acc *= n;
n--;
}
}
方法之前所積累下的各種狀態對于遞歸調用結果已經沒有任何意義,因此完全可以把本次方法中留在堆棧中的數據完全清除,把空間讓給最后的遞歸調用。這樣 的優化便使得遞歸不會在調用堆棧上產生堆積,意味著即時是“無限”遞歸也不會讓堆棧溢出”。這其實才是尾遞歸的“正統”優化方式,那么我們先暫時忘記之前 的“循環優化”,從最簡單的示例中查看這樣的優化是如何進行的。
在這里,感謝老趙同志對基礎的透徹講解。
浙公網安備 33010602011771號