堆和棧都是一種數據項按序排列的數據結構,只能在一端(稱為棧頂(top))對數據項進行插入和刪除。 堆,隊列優先,先進先出(FIFO—first in first out); 棧,先進後出(FILO—First-In/Last-Out)。 一般情況下,如果有人把堆棧合起來說,那它的意思是棧,而不是堆。 ...
堆和棧都是一種數據項按序排列的數據結構,只能在一端(稱為棧頂(top))對數據項進行插入和刪除。
堆,隊列優先,先進先出(FIFO—first in first out);
棧,先進後出(FILO—First-In/Last-Out)。
一般情況下,如果有人把堆棧合起來說,那它的意思是棧,而不是堆。
堆棧空間分配
1.棧區(stack):由編譯器自動分配釋放,存放函數的參數值,局部變數等值。其操作方式類似於數據結構中的棧。
2.堆區(heap):一般由程式員分配釋放,若程式員不釋放,則可能會引起記憶體泄漏。其類似於鏈表。
堆棧緩存方式
iOS 中應用程式使用的電腦記憶體不是統一分配空間,運行代碼使用的空間在三個不同的記憶體區域,分成三個段:“text segment “,“stack segment ”,“heap segment ”。
代碼區(text segment ):
是應用程式運行時應用程式代碼存在的記憶體段,運行前就已經確定(編譯時確定),通常為只讀的。代碼區的指令中包括操作碼和要操作的對象(或對象地址引用),代碼區指令根據程式設計流程依次執行,每一個指令,每一個單個函數、過程、方法和執行代碼都存在這個記憶體段中直到應用程式退出。一般使用中很少涉及。
棧(Stack):
當我們創建一個值類型,如結構體,系統將其存儲在一個被稱為棧的記憶體區域中,是由CPU直接管理和優化的。當一個函數聲明一個變數,變數將存儲在棧中,當函數調用完畢後棧會自動釋放該變數。因此棧是非常易於管理的、有效的,由於是CPU直接控制,速度非常快。
堆(Heap):
當我們創建了一個引用類型,如類,系統將把類實例存儲在一個被稱為堆的記憶體區域中。系統使用堆來存儲其他對象引用的數據。
堆是一個大的記憶體池,系統可以從該池中請求並動態分配記憶體塊。堆不會像棧一樣自動釋放對象,需要額外的工作來完成。這使得在堆中創建和刪除數據比棧慢。
棧使用的是一級緩存, 他們通常都是被調用時處於存儲空間中,調用完畢立即釋放。
堆則是存放在二級緩存中,生命周期由虛擬機的垃圾回收演算法來決定(並不是一旦成為孤兒對象就能被回收)。所以調用這些對象的速度要相對來得低一些。
stack 中的一個指針僅僅是一個整型變數,保存了heap(堆)中特定記憶體地址的數據。簡而言之,操作系統使用stack 段中的指針值訪問heap 段中的對象。如果stack 對象的指針沒有了,則heap 中的對象就不能訪問。這也是記憶體泄露的原因。
在iOS 操作系統的stack 段和heap 段中,你都可以創建數據對象。
stack 對象的優點主要有兩點,一是創建速度快,二是管理簡單,它有嚴格的生命周期。stack 對象的缺點是它不靈活。創建時長度是多大就一直是多 大,創建時是哪個函數創建的,它的owner 就一直是它。不像heap 對象那樣有多個owner ,其實多個owner 等同於引用計數。只有 heap 對象才是採用“引用計數”方法管理它。
作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:519832104 不管你是小白還是大牛歡迎入駐,分享經驗,討論技術,大家一起交流學習成長!
另附上一份各好友收集的大廠面試題,需要iOS開發學習資料、面試真題,可以添加iOS開發進階交流群,進群可自行下載!
堆棧數據結構區別
堆(數據結構):堆可以被看成是一棵樹,如:堆排序。
棧(數據結構):一種先進後出的數據結構。
堆和棧究竟有什麼區別? 主要的區別由以下幾點:
1、管理方式不同;
管理方式:對於棧來講,是由編譯器自動管理,無需我們手工控制;對於堆來說,釋放工作由程式員控制,容易產生memory leak。
2、空間大小不同;
空間大小:棧是一塊空間較小,但是運行速度很快的記憶體區域。棧上的記憶體分配遵循後進先出的原則,通過移動棧的尾指針實現 push(入棧)和 pop(出棧)操作。我們的程式是由一個個方法組成的,CPU 會負責調度並執行這些方法。當我們的程式執行到某個方法的時候,需要在棧上為方法需要的記憶體開闢空間,此時把棧的尾指針向棧底移動。當方法執行完畢後需要釋放掉這些空間,此時會把棧的尾指針移向棧頂,這就完成了一次棧上的記憶體分配。只要棧的剩餘空間大於stack 對象申請創建的空間,操作系統就會為程式提供這段記憶體空間,否則將報異常提示棧溢出。
堆是記憶體中的另一塊區域,空間比棧大的多,但是運行速度要比棧上的運行速度慢。堆可以在運行時動態的分配記憶體,補充棧上記憶體分配的不足。一般來講在32位系統下,堆記憶體可以達到4G的空間,從這個角度來看堆記憶體幾乎是沒有什麼限制的。
操作系統對於記憶體heap 段是採用鏈表進行管理的。操作系統有一個記錄空閑記憶體地址的鏈表,當收到程式的申請時,會遍歷鏈表,尋找第一個空間大於所申請的heap 節點,然後將該節點從空閑節點鏈表中刪除,並將該節點的空間分配給程式。iOS使用了名為 ARC(自動引用計數)的技術。在多線程環境中,多個線程會共用堆上的記憶體,為了確保線程安全,不得不在堆上進行加鎖操作,但是加鎖操作是很耗費性能的,你在堆上所獲的的數據安全性實際上是在犧牲性能的代價下得來的。
3、能否產生碎片不同;
碎 片問題:對於堆來講,頻繁的new/delete勢必會造成記憶體空間的不連續,從而造成大量的碎片,使程式效率降低。對於棧來講,則不會存在這個問題,因 為棧是先進後出的隊列,他們是如此的一一對應,以至於永遠都不可能有一個記憶體塊從棧中間彈出,在他彈出之前,在他上面的後進的棧內容已經被彈出。
4、生長方向不同;
生長方向:對於堆來講,生長方向是向上的,也就是向著記憶體地址增加的方向;對於棧來講,它的生長方向是向下的,是向著記憶體地址減小的方向增長。
5、分配方式不同;
分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變數的分配。動態分配由alloca函數進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。
6、分配效率不同;
分 配效率:棧是機器系統提供的數據結構,電腦會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較 高。堆則是C/C++函數庫提供的,它的機制是很複雜的,例如為了分配一塊記憶體,庫函數會按照一定的演算法(具體的演算法可以參考數據結構/操作系統)在堆內 存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由於記憶體碎片太多),就有可能調用系統功能去增加程式數據段的記憶體空間,這樣就有機會分到 足夠大小的記憶體,然後進行返回。顯然,堆的效率比棧要低得多。
從這裡我們可以看到,堆和棧相比,由於大量new/delete的使用,容 易造成大量的記憶體碎片;由於沒有專門的系統支持,效率很低;由於可能引發用戶態和核心態的切換,記憶體的申請,代價變得更加昂貴。
所以棧在程式中是應用最廣 泛的,就算是函數的調用也利用棧去完成,函數調用過程中的參數,返回地址,局部變數都採用棧的方式存放。
但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。另外,棧數據在多個線程或者多個棧之間是不可以共用的,但是在棧內部多個值相等的變數是可以指向一個地址的。和堆相比不是那麼靈活,有時候分配大量的記憶體空間,還是用堆好一些。無論是堆還是 棧,都要防止越界現象的發生(除非你是故意使其越界),因為越界的結果要麼是程式崩潰,要麼是摧毀程式的堆、棧結構,產生以想不到的結果,就算是在你的程 序運行過程中,沒有發生上面的問題,你還是要小心,說不定什麼時候就崩掉了。
Swift中的使用
Swift 中的數據類型分為引用類型(類)和值類型(枚舉、結構體)。引用類型存儲在 “堆” 上,值類型存儲在 “棧” 上。Swift 管理引用類型採用自動引用計數(ARC)的管理方法。值類型是由處理器來管理的,不需要程式員來管理。
在 Swift 中,典型的有 struct,enum,以及 tuple 都是值類型。而平時使用的Int,Double,Float,String,Array,Dictionary,Set 其實都是用結構體實現的,也是值類型。Swift 中,值類型的賦值為深拷貝(Deep Copy),值語義(Value Semantics)即新對象和源對象是獨立的,當改變新對象的屬性,源對象不會受到影響,反之同理。
在 Swift 中,class 和閉包是引用類型。引用類型的賦值是淺拷貝(Shallow Copy),引用語義(Reference Semantics)即新對象和源對象的變數名不同,但其引用(指向的記憶體空間)是一樣的,因此當使用新對象操作其內部數據時,源對象的內部數據也會受到影響。
值類型作為參數傳入時,函數體內部不能修改其值。引用類型作為參數傳入時,函數體內部不能修改其指向的記憶體地址,但是可以修改其內部的變數值。
值類型的優點是:不變性,值類型的變數是嚴格的被一個所有者控制的;獨立性,引用類型是相互依賴的,是一種隱式的依賴;還有可交換性。
對於面向對象編程,由於實例對象是可變的,導致對象的另一個享有者在合適的時候會去改變這個對象的屬性。swift支持類的單繼承,導致從多個class繼承到更多地功能,增加了複雜度,並且會導致class緊耦合的問題。在多線程情況下,可以同時改變同一個引用。
選擇值類型而不是引用類型的一個主要原因是能讓你的代碼變得更加簡單。Swift的核心是面向協議,引用類型有許多的享有者。值類型被賦給一個變數或者常量,傳給函數做參數時是它的值被拷貝的。這就讓值類型在任何時候只有一個享有者,從而降低複雜度。你在任何情況下用一個值類型,都能夠假設你的其他代碼不會使它改變,這通常在多線程環境中很有用,如果一個線程中使用的數據被另一個線程給意外的修改了,這通常會產生非常嚴重的Bug,且相當難以調試。Class = 高複雜度,值 = 低複雜度。而且,swift對值類型的操作上進行了一些優化,因此才有了swift大量使用值類型代替引用類型的說法。
由於只有當你需要修改數據時兩者的區別才會得到體現,所以當你的實例不會對數據進行修改的時候,值類型和引用類型看起來是完全相同的。你也許會想,寫一個完全不可變的類,通過使用不可變的存儲屬性,以及避免暴露修改數據的介面,從而在Swift里實現一個不可變的類。事實上,大多數的Cocoa類,比如NSURL等,都被設計為不可變的類,然而,Swift當前並沒有提供任何語言機制去強制申明一個類不可改變(比如子類化就能修改一個類的實現),只有結構體和枚舉才是強制不可變的。
在Swift里,Array、String和Dictionary都是值類型,他們的行為和C語言中的int類似,每個實例都有自己的數據,你不需要額外做任何事情,比如做一個顯式的copy,防止其他代碼在你不知情的情況下修改等,更重要的是,你能安全地線上程間傳遞它,而不需要使用同步技術。在提高安全性的精神下,這個模型將幫助你在Swift中寫出更多可預知的代碼。
除此之外,Swift和OC還有其他的類型對應,對應關係如下:
但是,需要關註的是,對於原來OC中的數據的引用類型,swift中並沒有真正完全的實現一套數據存儲邏輯。只是內部保存了對oc對象的引用,使得swift api訪問時行為邏輯和值類型一致