当前位置: 首页 > 科技观察

iOS中堆和栈的使用方法

时间:2023-03-14 18:04:43 科技观察

堆和栈都是数据项按顺序排列的数据结构,数据项只能在一端(称为栈顶)进行插入和删除。堆,先排队,先进先出(FIFO——firstinfirstout);栈,先进后出(FILO——先进后出)。一般来说,如果有人说stacktogether,就是stack的意思,不是heap。栈空间分配栈区(stack):由编译器自动分配和释放,存放函数参数值、局部变量等值。它像数据结构中的堆栈一样运行。堆区(heap):一般由程序员分配和释放,如果程序员不释放,可能会造成内存泄漏。它类似于链表。栈缓存方式iOS中应用程序使用的电脑内存并不是统一分配的。运行代码使用的空间在三个不同的内存区,分为三个段:“文本段”、“堆栈段”、“堆段”。代码区(textsegment):是应用程序运行时应用代码所在的内存段,在运行前确定(编译时确定),通常是只读的。代码区的指令包括操作码和要操作的对象(或对象地址引用)。代码区中的指令按照编程流程依次执行。每条指令、每个函数、过程、方法和执行代码都存在于该内存段中,直到应用程序退出。它很少涉及一般用途。栈:当我们创建一个值类型,比如结构体时,系统将其存储在一个称为栈的内存区域中,由CPU直接管理和优化。当一个函数声明一个变量时,这个变量会被存放在栈中,当函数调用完成时栈会自动释放这个变量。因此,堆栈非常易于管理、高效且非常快,因为它直接由CPU控制。堆:当我们创建了一个引用类型,比如一个类,系统会将类实例存放在一个叫做堆的内存区域中。系统使用堆来存储其他对象引用的数据。堆是一个大的内存池,系统可以从中请求和动态分配内存块。堆不会像堆栈那样自动释放对象,需要额外的工作才能做到这一点。这使得在堆上创建和删除数据比在堆栈上慢。堆栈使用一级缓存。它们通常在调用时存储在存储空间中,调用后立即释放。堆存放在二级缓存中,其生命周期由虚拟机的垃圾回收算法决定(一旦成为孤儿对象就无法回收)。所以调用这些对象的速度比较低。堆栈中的指针只是一个整数变量,它在堆中的特定内存地址保存数据。简而言之,操作系统使用栈段中的指针值来访问堆段中的对象。如果栈对象的指针没有了,就无法访??问堆中的对象。这也是内存泄漏的原因。您可以在iOS操作系统的堆栈段和堆段中创建数据对象。栈对象的优点主要有两个,一是创建速度快,二是容易管理,有严格的生命周期。堆栈对象的缺点是它不灵活。长度总是和创建的时候一样大,创建的时候是哪个函数创建的,它的所有者永远是它。与堆对象有多个所有者不同,实际上多个所有者相当于引用计数。只有堆对象使用“引用计数”的方式来管理它。栈数据结构的区别Heap(数据结构):堆可以看成是一棵树,如:堆排序。堆栈(数据结构):一种先进后出的数据结构。堆和栈有什么区别?主要区别如下:1、管理方式不同;管理方式:对于栈,由编译器自动管理,无需我们手动控制;程序员控制,容易出现内存泄漏。2、空间大小不同;空间大小:栈是一块空间小但运行速度快的内存区域。栈上的内存分配遵循后进先出的原则,通过移动栈尾指针实现push(入栈)和pop(出栈)操作。我们的程序是由方法组成的,CPU会负责调度执行这些方法。当我们的程序执行到某个方法时,需要在栈上开辟空间用于方法所需的内存,这时候将栈尾指针移动到栈底。方法执行后,需要释放这些空间。这时栈尾指针就会被移到栈顶,这样就完成了一次栈上的内存分配。只要栈的剩余空间大于栈对象申请创建的空间,操作系统就会为程序提供这块内存空间,否则会报异常,提示栈溢出。堆是内存中另一个比栈大得多的区域,但运行速度比栈慢。堆可以在运行时动态分配内存,以弥补栈上内存分配的不足。一般来说,在32位系统下,堆内存可以达到4G。从这个角度看,堆内存几乎没有限制。操作系统使用链表来管理内存堆段。操作系统有一个记录空闲内存地址的链表。当收到程序的申请时,会遍历链表,找到第一个空间大于所请求堆节点的堆节点,然后将该节点从空闲节点链表中删除,并删除该节点分配给该节点的空间程序。iOS使用了一种称为ARC(自动引用计数)的技术。在多线程环境下,多个线程会共享堆上的内存。为了保证线程安全,不得不对堆进行加锁操作,但是加锁操作是非常耗性能的。您在堆上获得的数据是安全的。性能实际上是以牺牲性能为代价的。NSString的对象是栈中的对象,NSMutableString的对象是堆中的对象。前者分配的内存长度是固定的,不能修改;后者分配的内存长度可变,可以有多个所有者,适用于计数管理内存管理方式。3、是否能产生分片不同;碎片问题:对于堆来说,频繁的new/delete必然会造成内存空间的不连续,从而产生大量的碎片,降低程序效率。对于栈来说,这个问题是不存在的,因为栈是一个先进后出的队列,他们是一一对应的,永远不可能有一个内存块从中间弹出堆。它上面的后进栈的内容已经被弹出。4、生长方向不同;增长方向:对于堆来说,增长方向是向上的,即内存地址递增的方向;对于栈来说,它的增长方向是向下的,也就是朝着内存地址增长的方向递减。5、分配方式不同;分配方式:堆是动态分配的,没有静态分配的堆。栈的分配有两种方式:静态分配和动态分配。静态分配由编译器完成,就像局部变量的分配一样。动态分配是通过alloca函数分配的,但是栈的动态分配不同于堆。他的动态分配是编译器释放的,不需要我们手动实现。6.分配效率不同;分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配一个专门的寄存器来存放栈的地址,入栈和出栈都有专门的指令栈,决定了栈的效率更高。堆由C/C++函数库提供,其机制非常复杂。例如,为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的数据(具体算法请参考数据结构/操作系统)。如果空间不够(可能是内存碎片太多),可以调用系统函数增加程序数据段的内存空间,这样才有机会分配足够的内存,然后执行return.很明显,堆比栈效率低很多。从这里我们可以看出,与栈相比,由于使用了大量的new/delete,很容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;因为这可能会导致用户态和核心态的切换,内存的申请变得更加昂贵。因此,栈在程序中的应用最为广泛。甚至函数调用也是使用栈完成的。函数调用过程中的参数、返回地址、局部变量都存放在栈中。因此我们建议大家尽量使用栈而不是堆。但缺点是必须确定栈中存储数据的大小和生存期,缺乏灵活性。另外,栈数据不能在多个线程或多个栈之间共享,但栈内部的多个等值变量可以指向一个地址。和堆相比,就没那么灵活了。有时使用堆来分配大量的内存空间会更好。不管是堆还是栈,都要防止越界现象的发生(除非你故意让它越界),因为越界的结果要么是程序崩溃,要么程序的堆和栈结构被破坏,导致意想不到的结果。在你的程序运行过程中,如果没有出现以上问题,你还是要小心,它随时可能崩溃。Swift中的数据类型分为引用类型(类)和值类型(枚举、结构)。引用类型存储在“堆”中,值类型存储在“堆栈”中。Swift使用自动引用计数(ARC)管理来管理引用类型。值类型由处理器管理,而不是由程序员管理。在Swift中,通常struct、enum和tuple是值类型。平时用到的Int、Double、Float、String、Array、Dictionary、Set其实都是用结构体实现的,也是值类型。在Swift中,值类型的赋值是DeepCopy,值语义(ValueSemantics)意味着新对象和源对象是独立的。当新对象的属性改变时,源对象不会受到影响,反之亦然。在Swift中,类和闭包是引用类型。引用类型的赋值是浅拷贝(ShallowCopy),引用语义(ReferenceSemantics)是指新对象和源对象的变量名不同,但它们的引用(指向的内存空间)是同样,所以在使用新对象操作其内部数据时,源对象的内部数据也会受到影响。当一个值类型作为参数传入时,它的值不能在函数体内被修改。当一个引用类型作为参数传入时,它指向的内存地址在函数体内是不能修改的,但是它里面的变量值是可以修改的。值类型的优点是:不变性,值类型的变量被所有者严格控制;独立性,引用类型是相互依赖的,这是一种隐式依赖;和可交换性。对于面向对象的编程,由于实例对象是可变的,对象的另一个拥有者会在适当的时候改变对象的属性。Swift支持类的单继承,这就导致多个类继承的功能较多,增加了复杂度,造成了类紧耦合的问题。在多线程的情况下,可以同时更改同一个引用。选择值类型而不是引用类型的主要原因之一是使您的代码更简单。Swift的核心是面向协议的,引用类型有很多所有者。值类型被分配给变量或常量,当它的值作为参数传递给函数时被复制。这通过允许值类型在任何时候只有一个所有者来降低复杂性。在任何情况下你使用一个值类型,你可以假设你的其他代码不会改变它。这在多线程环境中通常很有用。如果一个线程使用的数据被另一个线程不小心修改了,这通常会产生非常严重的错误,很难调试。类=高复杂性,值=低复杂性。而且swift对值类型的操作做了一些优化,所以说swift大量使用值类型代替引用类型。由于两者之间的差异仅在您需要修改数据时体现出来,因此当您的实例不修改数据时,值类型和引用类型看起来完全一样。您可能会想,编写一个完全不可变的类,并通过使用不可变的存储属性在Swift中实现一个不可变的类,并避免暴露修改数据的接口。事实上,大多数Cocoa类,例如NSURL,都被设计为不可变的。然而,Swift目前并没有提供任何语言机制来强制类不可变(例如,子类化可以修改类的实现),只有结构体和枚举是强制不可变的。在Swift中,Array、String和Dictionary都是值类型。它们的行为类似于C语言中的整数。每个实例都有自己的数据。您不需要做任何额外的事情,例如制作显式副本。防止其他代码在你不知情的情况下被修改等等。更重要的是,你可以在不使用同步技术的情况下安全地在线程之间传递它。本着提高安全性的精神,该模型将帮助您在Swift中编写更可预测的代码。除此之外,Swift与OC之间还有其他类型的对应关系,对应关系如下:不过需要注意的是,对于原始OC中数据的引用类型,Swift并没有真正完整的实现一组数据存储逻辑。内部只保存了oc对象的引用,这样访问swiftapi时行为逻辑和值类型是一致的。