堆和栈在iOS开发中的使用

堆和栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。程序员

堆,队列优先,先进先出(FIFO—first in first out);面试

栈,先进后出(FILO—First-In/Last-Out)。算法

通常状况下,若是有人把堆栈合起来讲,那它的意思是栈,而不是堆。编程

堆栈空间分配

1.栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量等值。其操做方式相似于数据结构中的栈。swift

2.堆区(heap):通常由程序员分配释放,若程序员不释放,则可能会引发内存泄漏。其相似于链表。api

堆栈缓存方式

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开发进阶交流群,进群可自行下载!

堆栈数据结构区别

堆(数据结构):堆能够被当作是一棵树,如:堆排序。

栈(数据结构):一种先进后出的数据结构。

堆和栈究竟有什么区别? 主要的区别由如下几点:

一、管理方式不一样;

管理方式:对于栈来说,是由编译器自动管理,无需咱们手工控制;对于堆来讲,释放工做由程序员控制,容易产生memory leak。

二、空间大小不一样;

空间大小:栈是一块空间较小,可是运行速度很快的内存区域。栈上的内存分配遵循后进先出的原则,经过移动栈的尾指针实现 push(入栈)和 pop(出栈)操做。咱们的程序是由一个个方法组成的,CPU 会负责调度并执行这些方法。当咱们的程序执行到某个方法的时候,须要在栈上为方法须要的内存开辟空间,此时把栈的尾指针向栈底移动。当方法执行完毕后须要释放掉这些空间,此时会把栈的尾指针移向栈顶,这就完成了一次栈上的内存分配。只要栈的剩余空间大于stack 对象申请建立的空间,操做系统就会为程序提供这段内存空间,不然将报异常提示栈溢出。

堆是内存中的另外一块区域,空间比栈大的多,可是运行速度要比栈上的运行速度慢。堆能够在运行时动态的分配内存,补充栈上内存分配的不足。通常来说在32位系统下,堆内存能够达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。

操做系统对于内存heap 段是采用链表进行管理的。操做系统有一个记录空闲内存地址的链表,当收到程序的申请时,会遍历链表,寻找第一个空间大于所申请的heap 节点,而后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。iOS使用了名为 ARC(自动引用计数)的技术。在多线程环境中,多个线程会共享堆上的内存,为了确保线程安全,不得不在堆上进行加锁操做,可是加锁操做是很耗费性能的,你在堆上所获的的数据安全性其实是在牺牲性能的代价下得来的。

三、可否产生碎片不一样;

碎 片问题:对于堆来说,频繁的new/delete势必会形成内存空间的不连续,从而形成大量的碎片,使程序效率下降。对于栈来说,则不会存在这个问题,因 为栈是先进后出的队列,他们是如此的一一对应,以致于永远都不可能有一个内存块从栈中间弹出,在他弹出以前,在他上面的后进的栈内容已经被弹出。

四、生长方向不一样;

生长方向:对于堆来说,生长方向是向上的,也就是向着内存地址增长的方向;对于栈来说,它的生长方向是向下的,是向着内存地址减少的方向增加。

五、分配方式不一样;

分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,好比局部变量的分配。动态分配由alloca函数进行分配,可是栈的动态分配和堆是不一样的,他的动态分配是由编译器进行释放,无需咱们手工实现。

六、分配效率不一样;

分 配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较 高。堆则是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大牛交流学习

相关文章
相关标签/搜索