堆和棧都是一種數(shù)據(jù)項按序排列的數(shù)據(jù)結(jié)構(gòu)夺艰,只能在一端(稱為棧頂(top))對數(shù)據(jù)項進行插入和刪除。
堆:隊列優(yōu)先,先進先出(FIFO—first in first out)老翘。
棧:先進后出(FILO—First-In/Last-Out)。
1.棧區(qū)(stack):由編譯器自動分配釋放美旧,存放函數(shù)的參數(shù)值,局部變量等值贬墩。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧榴嗅。
2.堆區(qū)(heap):一般由程序員分配釋放,若程序員不釋放陶舞,則可能會引起內(nèi)存泄漏嗽测。其類似于鏈表。
iOS 中應(yīng)用程序使用的計算機內(nèi)存不是統(tǒng)一分配空間肿孵,運行代碼使用的空間在三個不同的內(nèi)存區(qū)域唠粥,分成三個段:“text segment “,“stack segment ”停做,“heap segment ”晤愧。
代碼區(qū)(text segment ):是應(yīng)用程序運行時應(yīng)用程序代碼存在的內(nèi)存段,運行前就已經(jīng)確定(編譯時確定)蛉腌,通常為只讀的官份。代碼區(qū)的指令中包括操作碼和要操作的對象(或?qū)ο蟮刂芬茫a區(qū)指令根據(jù)程序設(shè)計流程依次執(zhí)行烙丛,每一個指令贯吓,每一個單個函數(shù)、過程蜀变、方法和執(zhí)行代碼都存在這個內(nèi)存段中直到應(yīng)用程序退出悄谐。一般使用中很少涉及。
棧(Stack):當(dāng)我們創(chuàng)建一個值類型库北,如結(jié)構(gòu)體爬舰,系統(tǒng)將其存儲在一個被稱為棧的內(nèi)存區(qū)域中们陆,是由CPU直接管理和優(yōu)化的。當(dāng)一個函數(shù)聲明一個變量情屹,變量將存儲在棧中坪仇,當(dāng)函數(shù)調(diào)用完畢后棧會自動釋放該變量。因此棧是非常易于管理的垃你、有效的椅文,由于是CPU直接控制,速度非诚模快皆刺。
堆(Heap):當(dāng)我們創(chuàng)建了一個引用類型,如類凌摄,系統(tǒng)將把類實例存儲在一個被稱為堆的內(nèi)存區(qū)域中羡蛾。系統(tǒng)使用堆來存儲其他對象引用的數(shù)據(jù)。堆是一個大的內(nèi)存池锨亏,系統(tǒng)可以從該池中請求并動態(tài)分配內(nèi)存塊痴怨。堆不會像棧一樣自動釋放對象,需要額外的工作來完成器予。這使得在堆中創(chuàng)建和刪除數(shù)據(jù)比棧慢浪藻。
棧使用的是一級緩存, 他們通常都是被調(diào)用時處于存儲空間中乾翔,調(diào)用完畢立即釋放珠移。堆則是存放在二級緩存中,生命周期由虛擬機的垃圾回收算法來決定(并不是一旦成為孤兒對象就能被回收)末融。所以調(diào)用這些對象的速度要相對來得低一些。
stack 中的一個指針僅僅是一個整型變量暇韧,保存了heap(堆)中特定內(nèi)存地址的數(shù)據(jù)勾习。簡而言之,操作系統(tǒng)使用stack 段中的指針值訪問heap 段中的對象懈玻。如果stack 對象的指針沒有了巧婶,則heap 中的對象就不能訪問。這也是內(nèi)存泄露的原因涂乌。
在iOS 操作系統(tǒng)的stack 段和heap 段中艺栈,你都可以創(chuàng)建數(shù)據(jù)對象。stack 對象的優(yōu)點主要有兩點湾盒,一是創(chuàng)建速度快湿右,二是管理簡單,它有嚴格的生命周期罚勾。stack 對象的缺點是它不靈活毅人。創(chuàng)建時長度是多大就一直是多 大吭狡,創(chuàng)建時是哪個函數(shù)創(chuàng)建的,它的owner 就一直是它丈莺。不像heap 對象那樣有多個owner 划煮,其實多個owner 等同于引用計數(shù)。只有 heap 對象才是采用“引用計數(shù)”方法管理它缔俄。
堆棧數(shù)據(jù)結(jié)構(gòu)區(qū)別
堆(數(shù)據(jù)結(jié)構(gòu)):堆可以被看成是一棵樹弛秋,如:堆排序。
棧(數(shù)據(jù)結(jié)構(gòu)):一種先進后出的數(shù)據(jù)結(jié)構(gòu)俐载。
堆和棧究竟有什么區(qū)別蟹略? 主要的區(qū)別由以下幾點:
1、管理方式不同瞎疼;
管理方式:對于棧來講科乎,是由編譯器自動管理,無需我們手工控制贼急;對于堆來說茅茂,釋放工作由程序員控制,容易產(chǎn)生memory leak太抓。
2空闲、空間大小不同;
空間大凶叩小:棧是一塊空間較小碴倾,但是運行速度很快的內(nèi)存區(qū)域。棧上的內(nèi)存分配遵循后進先出的原則掉丽,通過移動棧的尾指針實現(xiàn) push(入棧)和 pop(出棧)操作跌榔。我們的程序是由一個個方法組成的,CPU 會負責(zé)調(diào)度并執(zhí)行這些方法捶障。當(dāng)我們的程序執(zhí)行到某個方法的時候僧须,需要在棧上為方法需要的內(nèi)存開辟空間,此時把棧的尾指針向棧底移動项炼。當(dāng)方法執(zhí)行完畢后需要釋放掉這些空間担平,此時會把棧的尾指針移向棧頂,這就完成了一次棧上的內(nèi)存分配锭部。只要棧的剩余空間大于stack 對象申請創(chuàng)建的空間暂论,操作系統(tǒng)就會為程序提供這段內(nèi)存空間,否則將報異常提示棧溢出拌禾。
堆是內(nèi)存中的另一塊區(qū)域取胎,空間比棧大的多,但是運行速度要比棧上的運行速度慢湃窍。堆可以在運行時動態(tài)的分配內(nèi)存扼菠,補充棧上內(nèi)存分配的不足摄杂。一般來講在32位系統(tǒng)下,堆內(nèi)存可以達到4G的空間循榆,從這個角度來看堆內(nèi)存幾乎是沒有什么限制的析恢。
操作系統(tǒng)對于內(nèi)存heap 段是采用鏈表進行管理的。操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表秧饮,當(dāng)收到程序的申請時映挂,會遍歷鏈表,尋找第一個空間大于所申請的heap 節(jié)點盗尸,然后將該節(jié)點從空閑節(jié)點鏈表中刪除柑船,并將該節(jié)點的空間分配給程序。iOS使用了名為 ARC(自動引用計數(shù))的技術(shù)泼各。在多線程環(huán)境中鞍时,多個線程會共享堆上的內(nèi)存,為了確保線程安全扣蜻,不得不在堆上進行加鎖操作逆巍,但是加鎖操作是很耗費性能的,你在堆上所獲的的數(shù)據(jù)安全性實際上是在犧牲性能的代價下得來的莽使。
NSString 的對象就是stack 中的對象锐极,NSMutableString 的對象就是heap 中的對象。前者創(chuàng)建時分配的內(nèi)存長度固定且不可修改芳肌;后者是分配內(nèi)存長度是可變的灵再,可有多個owner, 適用于計數(shù)管理內(nèi)存管理模式。
3亿笤、能否產(chǎn)生碎片不同翎迁;
碎 片問題:對于堆來講,頻繁的new/delete勢必會造成內(nèi)存空間的不連續(xù)净薛,從而造成大量的碎片汪榔,使程序效率降低。對于棧來講罕拂,則不會存在這個問題,因 為棧是先進后出的隊列全陨,他們是如此的一一對應(yīng)爆班,以至于永遠都不可能有一個內(nèi)存塊從棧中間彈出,在他彈出之前辱姨,在他上面的后進的棧內(nèi)容已經(jīng)被彈出柿菩。
4、生長方向不同雨涛;
生長方向:對于堆來講枢舶,生長方向是向上的懦胞,也就是向著內(nèi)存地址增加的方向;對于棧來講凉泄,它的生長方向是向下的躏尉,是向著內(nèi)存地址減小的方向增長。
5后众、分配方式不同胀糜;
分配方式:堆都是動態(tài)分配的,沒有靜態(tài)分配的堆蒂誉。棧有2種分配方式:靜態(tài)分配和動態(tài)分配教藻。靜態(tài)分配是編譯器完成的,比如局部變量的分配右锨。動態(tài)分配由alloca函數(shù)進行分配括堤,但是棧的動態(tài)分配和堆是不同的,他的動態(tài)分配是由編譯器進行釋放绍移,無需我們手工實現(xiàn)悄窃。
6、分配效率不同登夫;
分 配效率:棧是機器系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu)广匙,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行恼策,這就決定了棧的效率比較 高鸦致。堆則是C/C++函數(shù)庫提供的,它的機制是很復(fù)雜的涣楷,例如為了分配一塊內(nèi)存分唾,庫函數(shù)會按照一定的算法(具體的算法可以參考數(shù)據(jù)結(jié)構(gòu)/操作系統(tǒng))在堆內(nèi) 存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內(nèi)存碎片太多)狮斗,就有可能調(diào)用系統(tǒng)功能去增加程序數(shù)據(jù)段的內(nèi)存空間绽乔,這樣就有機會分到 足夠大小的內(nèi)存,然后進行返回碳褒。顯然折砸,堆的效率比棧要低得多。
從這里我們可以看到沙峻,堆和棧相比睦授,由于大量new/delete的使用,容 易造成大量的內(nèi)存碎片摔寨;由于沒有專門的系統(tǒng)支持去枷,效率很低;由于可能引發(fā)用戶態(tài)和核心態(tài)的切換,內(nèi)存的申請删顶,代價變得更加昂貴竖螃。所以棧在程序中是應(yīng)用最廣 泛的,就算是函數(shù)的調(diào)用也利用棧去完成逗余,函數(shù)調(diào)用過程中的參數(shù)特咆,返回地址,局部變量都采用棧的方式存放猎荠。所以坚弱,我們推薦大家盡量用棧,而不是用 堆关摇。
但缺點是荒叶,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性输虱。另外些楣,棧數(shù)據(jù)在多個線程或者多個棧之間是不可以共享的,但是在棧內(nèi)部多個值相等的變量是可以指向一個地址的宪睹。和堆相比不是那么靈活愁茁,有時候分配大量的內(nèi)存空間,還是用堆好一些亭病。無論是堆還是 棧鹅很,都要防止越界現(xiàn)象的發(fā)生(除非你是故意使其越界),因為越界的結(jié)果要么是程序崩潰罪帖,要么是摧毀程序的堆促煮、棧結(jié)構(gòu),產(chǎn)生以想不到的結(jié)果,就算是在你的程 序運行過程中整袁,沒有發(fā)生上面的問題菠齿,你還是要小心,說不定什么時候就崩掉了坐昙。
Swift 中的數(shù)據(jù)類型分為引用類型(類)和值類型(枚舉绳匀、結(jié)構(gòu)體)。引用類型存儲在 “堆” 上炸客,值類型存儲在 “椉部茫” 上。Swift 管理引用類型采用自動引用計數(shù)(ARC)的管理方法痹仙。值類型是由處理器來管理的是尔,不需要程序員來管理。
在 Swift 中蝶溶,典型的有 struct嗜历,enum,以及 tuple 都是值類型抖所。而平時使用的Int梨州,Double,F(xiàn)loat田轧,String暴匠,Array,Dictionary傻粘,Set 其實都是用結(jié)構(gòu)體實現(xiàn)的每窖,也是值類型。Swift 中弦悉,值類型的賦值為深拷貝(Deep Copy)窒典,值語義(Value Semantics)即新對象和源對象是獨立的,當(dāng)改變新對象的屬性稽莉,源對象不會受到影響瀑志,反之同理。
在 Swift 中污秆,class 和閉包是引用類型劈猪。引用類型的賦值是淺拷貝(Shallow Copy),引用語義(Reference Semantics)即新對象和源對象的變量名不同良拼,但其引用(指向的內(nèi)存空間)是一樣的战得,因此當(dāng)使用新對象操作其內(nèi)部數(shù)據(jù)時,源對象的內(nèi)部數(shù)據(jù)也會受到影響庸推。
值類型作為參數(shù)傳入時常侦,函數(shù)體內(nèi)部不能修改其值。引用類型作為參數(shù)傳入時予弧,函數(shù)體內(nèi)部不能修改其指向的內(nèi)存地址刮吧,但是可以修改其內(nèi)部的變量值。
值類型的優(yōu)點是:不變性掖蛤,值類型的變量是嚴格的被一個所有者控制的杀捻;獨立性,引用類型是相互依賴的蚓庭,是一種隱式的依賴致讥;還有可交換性。
對于面向?qū)ο缶幊唐髟蓿捎趯嵗龑ο笫强勺兊墓父ぃ瑢?dǎo)致對象的另一個享有者在合適的時候會去改變這個對象的屬性。swift支持類的單繼承港柜,導(dǎo)致從多個class繼承到更多地功能请契,增加了復(fù)雜度咳榜,并且會導(dǎo)致class緊耦合的問題。在多線程情況下爽锥,可以同時改變同一個引用涌韩。
選擇值類型而不是引用類型的一個主要原因是能讓你的代碼變得更加簡單。Swift的核心是面向協(xié)議氯夷,引用類型有許多的享有者臣樱。值類型被賦給一個變量或者常量,傳給函數(shù)做參數(shù)時是它的值被拷貝的腮考。這就讓值類型在任何時候只有一個享有者雇毫,從而降低復(fù)雜度。你在任何情況下用一個值類型踩蔚,都能夠假設(shè)你的其他代碼不會使它改變棚放,這通常在多線程環(huán)境中很有用,如果一個線程中使用的數(shù)據(jù)被另一個線程給意外的修改了,這通常會產(chǎn)生非常嚴重的Bug,且相當(dāng)難以調(diào)試网杆。Class = 高復(fù)雜度,值 = 低復(fù)雜度孝冒。而且,swift對值類型的操作上進行了一些優(yōu)化拟杉,因此才有了swift大量使用值類型代替引用類型的說法庄涡。
由于只有當(dāng)你需要修改數(shù)據(jù)時兩者的區(qū)別才會得到體現(xiàn),所以當(dāng)你的實例不會對數(shù)據(jù)進行修改的時候搬设,值類型和引用類型看起來是完全相同的穴店。你也許會想,寫一個完全不可變的類拿穴,通過使用不可變的存儲屬性泣洞,以及避免暴露修改數(shù)據(jù)的接口,從而在Swift里實現(xiàn)一個不可變的類默色。事實上球凰,大多數(shù)的Cocoa類,比如NSURL等腿宰,都被設(shè)計為不可變的類呕诉,然而,Swift當(dāng)前并沒有提供任何語言機制去強制申明一個類不可改變(比如子類化就能修改一個類的實現(xiàn))吃度,只有結(jié)構(gòu)體和枚舉才是強制不可變的甩挫。
在Swift里,Array椿每、String和Dictionary都是值類型伊者,他們的行為和C語言中的int類似英遭,每個實例都有自己的數(shù)據(jù),你不需要額外做任何事情亦渗,比如做一個顯式的copy贪绘,防止其他代碼在你不知情的情況下修改等,更重要的是央碟,你能安全地在線程間傳遞它,而不需要使用同步技術(shù)均函。在提高安全性的精神下亿虽,這個模型將幫助你在Swift中寫出更多可預(yù)知的代碼。
除此之外苞也,Swift和OC還有其他的類型對應(yīng)洛勉,對應(yīng)關(guān)系如下:
但是,需要關(guān)注的是如迟,對于原來OC中的數(shù)據(jù)的引用類型收毫,swift中并沒有真正完全的實現(xiàn)一套數(shù)據(jù)存儲邏輯。只是內(nèi)部保存了對oc對象的引用殷勘,使得swift api訪問時行為邏輯和值類型一致此再。
轉(zhuǎn)載地址:https://blog.csdn.net/sun_cui_hua/article/details/107707339