Go语言中对于堆栈理解

在JAVA中相信大家对堆栈应该有个很清晰的认知。

什么是堆栈?
在计算机中堆栈的概念分为:数据结构的堆栈内存分配中堆栈

数据结构的堆栈:

堆:
堆可以被看成是一棵树,如:堆排序。在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。

栈:
—种先进后出的数据结构。这里着重讲的是内存分配中的堆和栈。

内存分配中的堆和栈

栈(操作系统)∶

由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆(操作系统)︰

一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。

堆栈缓存方式


栈使用的是一级缓存,他们通常都是被调用时处于存储空间中,调用完毕立即释放。


堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被
回收)。所以调用这些对象的速度要相对来得低一些。

栈总结

说白了,栈(Stack)是一种拥有特殊规则的线性表数据结构。
栈只允许从线性表的同一端放入和取出数据,按照后进先出(LIFO,Last InFirst Out)的顺序,如下图所示:

在这里插入图片描述

往栈中放入元素的过程叫做入栈。
入栈会增加栈的元素数量,最后放入的元素总是位于栈的顶部,最先放入的元素总是位于栈的底部。

从栈中取出元素时,只能从栈顶部取出。取出元素后,栈的元素数量会变少。最先放入的元素总是最后被取出,最后放入的元素总是最先被取出。不允许从栈底获取数据,也不允许对栈成员(除了栈顶部的成员)进行任何查看和修改操作。

栈的原理类似于将书籍一本一本地堆起来。书按顺序一本一本从顶部放入,要取书时只能从顶部一本一本取出。

堆总结

堆在内存分配中类似于往一个房间里摆放各种家具,家具的尺寸有大有小,分配内存时,需要找一块足够装下家具的空间再摆放家具。
经过反复摆放和腾空家具后,房间里的空间会变得乱七八糟,此时再往这个空间里摆放家具会发现虽然有足够的空间,但各个空间分布在不同的区域,没有一段连续的空间来摆放家具。此时,内存分配器就需要对这些空间进行调整优化,如下图所示:

在这里插入图片描述

堆分配内存和栈分配内存相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。

变量和栈关系

栈可用于内存分配,栈的分配和回收速度非常快。下面的代码展示了栈在内存分配上的作用:

func calc(a, b int) int {
    
    
    var c int        
    c = a * b      
    
    var x int       
    x = c * 10    
    
    return x

}

解析:

  • 第 1 行,传入 a、b 两个整型参数。
  • 第 2 行,声明整型变量 c,运行时,c 会分配一段内存用以存储 c 的数值。
  • 第 3 行,将 a 和 b 相乘后赋值给 c。
  • 第 5 行,声明整型变量 x,x 也会被分配一段内存。
  • 第 6 行,让 c 乘以 10 后赋值给变量 x。
  • 第 8 行,返回 x 的值。

上面的代码在没有任何优化的情况下,会进行变量 c 和 x 的分配过程。
Go语言默认情况下会将 c 和 x 分配在栈上,这两个变量在 calc() 函数退出时就不再使用,函数结束时,保存 c 和 x 的栈内存再出栈释放内存,整个分配内存的过程通过栈的分配和回收都会非常迅速。

堆和栈的分配

变量定义完成一般是分配在堆和栈空间上的,存在哪个空间上是根据你是否有动态分配内存有关(new/malloc)。
但是在Go语言上这个选择并不是基于使用var和new关键字来声明变量的。

示例一:

var p *int    //全局指针变量
func f(){
    
    
    var i int
    i = 1
    p = &i    //全局指针变量指向局部变量i
}

示例二:

func f(){
    
    
    p := new(int) //局部指针变量,使用new申请的空间
    *p = 1
}

第一个示例中,使用var定义局部变量,但是由于 将引用赋值给全局指针变量p,当函数结束,此时i并不会被释放,所以局部变量i是申请在堆上(程序员手动释放)。

第二个示例中,使用new申请空间,由于退出函数p会被释放,所以p是申请在栈上(自动释放)。

Go语言区别于C/C++,虽然变量申请在堆空间上,但是它有自动回收垃圾的功能,所以这些堆地址空间也无需我们手动回收,系统会在需要释放的时刻自动进行垃圾回收。

go语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有跑出函数范围,就可以在栈上,反之则必须分配在堆。

对于动态new出来的局部变量,go语言编译器也会根据是否有逃逸行为来决定是分配在堆还是栈,而不是直接分配在堆中。

最后结论就是一个函数内局部变量,不管是不是动态new出来的,它会被分配在堆还是栈,是由编译器做逃逸分析之后做出的决定。

后面会说到对变量逃逸做具体讲解,以此决定应该使用堆还是栈来进行内存分配。

猜你喜欢

转载自blog.csdn.net/zp17834994071/article/details/108759090