堆和棧在iOS開發中的使用

来源:https://www.cnblogs.com/chengxyyh/archive/2020/06/18/13157393.html
-Advertisement-
Play Games

堆和棧都是一種數據項按序排列的數據結構,只能在一端(稱為棧頂(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訪問時行為邏輯和值類型一致

點擊此處,立即與iOS大牛交流學習


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Redis集群 ·Redis集群提供了一種運行Redis安裝的方法,在該安裝中,數據會在多個Redis節點之間自動分片。 Redis集群在分區期間還提供了一定程度的可用性,這實際上是在某些節點出現故障或無法通信時有繼續工作的能力。但是,如果發生較嚴重故障(例如,大多數主節點不可用時),集群將停止運行 ...
  • 一個事務執行dml操作,就會自動加上行共用表鎖,以防止其他需要排他鎖的事務訪問。 一個事務對錶新增數據,另一個事務修改表報錯顯示“資源正忙...”,因為修改該表時需要排他鎖。 一個事務修改表數據,或刪除表數據,就是真正的影響記錄數為0,另一個事務依然無法修改該表,因為修改該表時需要排他鎖。 ...
  • 先上個pgsql操作的對比,然後引出ignite的並置處理規則 ddl: drop table student;CREATE TABLE student(sid int PRIMARY KEY ,sname varchar(64), cid int); drop table course;CREAT ...
  • 本文章更新於2020-06-14,使用MySQL 5.7,操作系統為Deepin 15.9。 說明:下文中,小寫為自定義變數,根據實際情況填寫(個別小寫的參數除外)。使用[]引起表示內容可選,使用{}引起表示內容為一個整體,|表示使用左側或右側內容,...表示重覆之前內容。 實際上,所有語句和命令的 ...
  • 前置條件 用戶有查詢數據統計許可權 GRANT VIEW DATABASE STATE TO database_user; CPU性能問題 正在發生 查看前X個CPU消耗查詢 (彙總) SELECT TOP 10 GETDATE() runtime, * FROM ( SELECT query_sta ...
  • 在Android我們經常會使用到EventBus來進行通信,常用到的場景就是不同的頁面來傳遞數據,Flutter中也可以通過EventBus來進行不同頁面間的數據傳遞。 EventBus就是一條事件訂閱匯流排,有事件的訂閱者、事件的發佈者。 demo就是模擬了一下頁面間的數據傳遞,頁面A跳轉到頁面... ...
  • 關於演算法的想法 由於面試可能需要手寫演算法,網上搜羅了一些資料,整理了下演算法的OC的實現代碼,雖然平時開發中一般用不到,但是多積累一些技術知識,還是對以後發展大有裨益的 github上搜集的幾大演算法原理和實現代碼,只有JavaScript、Python、Go、Java的實現代碼 演算法文字理解和OC代碼 ...
  • 這篇隨筆主要記錄的學習內容是GestureDetector手勢識別,內容包括識別單擊、雙擊、長按、組件拖拽和縮放處理。 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...