08 | 栈:如何实现浏览器的前进和后退功能
问题 1:我们在讲栈的应用时,讲到用函数调用栈来保存临时变量,为什么函数调用要用“栈”来保存临时变量呢?用其他数据结构不行吗?
问题 2:我们都知道,JVM 内存管理中有个“堆栈”的概念。栈内存用来存储局部变量和方法调用,堆内存用来存储 Java 中的对象。那 JVM 里面的“栈”跟我们这里说的“栈”是不是一回事呢?如果不是,那它为什么又叫作“栈”呢?
函数调用栈
函数调用栈是一种常用的实现方式,它基于栈(Stack)的后进先出(LIFO)原则。当一个函数被调用时,会创建一个新的栈帧(stack frame)来存储函数的局部变量、参数、返回地址等信息。这种使用栈的方式有几个优势:
内存管理简单: 函数调用栈的大小是动态管理的,栈帧的大小在编译时是可知的。当一个函数被调用时,分配一段连续的内存用于存储该函数的局部变量和其他信息。当函数调用结束后,这段内存就可以被释放,从而实现了内存的高效管理。
速度快: 栈是一种高效的数据结构,支持快速的入栈和出栈操作。函数的调用和返回都可以通过栈的压栈和弹栈操作来实现,速度相对较快。
嵌套调用: 函数调用栈天然支持嵌套调用。每次函数调用都创建一个新的栈帧,这使得在一个函数中调用另一个函数变得简单而直观。每个函数都有自己的一组局部变量,避免了不同函数之间的干扰。
递归支持: 函数调用栈的结构天生支持递归调用。每个递归调用都会创建一个新的栈帧,允许在相同函数内部重复执行。
虽然函数调用栈有这些优势,但在某些情况下,也可以使用其他数据结构来保存临时变量。例如,使用堆(Heap)或者自定义的数据结构。然而,这样的实现可能会更加复杂,需要手动管理内存,而且通常不如函数调用栈那样高效。因此,在绝大多数编程语言和情境中,函数调用栈是默认的选择。
在 JVM(Java Virtual Machine)中的"栈"和通常的数据结构中的"栈"有相似之处,但并不完全一样。以下是两者之间的一些区别和联系:
JVM 中的栈:
在 JVM 中,栈(Java Stack)主要用于存储线程的局部变量和方法调用信息。关键特点包括:
局部变量存储: 每个栈帧(stack frame)都包含了局部变量表,用于存储方法内部定义的局部变量,包括基本数据类型、对象引用以及方法返回地址等。
方法调用: 每次方法调用时,都会在栈上创建一个新的栈帧,用于存储该方法的局部变量和其他信息。方法返回时,对应的栈帧会被弹出。
后进先出(LIFO): 栈的操作是后进先出的,也就是说,最后进入栈的栈帧最先被执行和弹出。
通常数据结构中的栈:
在通常的数据结构中,栈是一种抽象数据类型,可以通过数组或链表实现。关键特点包括:
后进先出(LIFO): 栈的基本操作是压栈(push)和弹栈(pop),它们遵循后进先出的原则。
限定操作: 通常,栈有一定的容量限制,当栈满时无法再进行压栈操作。对于栈的实现,可以使用数组或链表等数据结构。
区别和联系:
尽管 JVM 中的"栈"和通常的数据结构中的"栈"都具有后进先出的特性,但它们的目的和使用场景不同。JVM 中的栈主要用于支持方法调用和局部变量的存储,而通常的数据结构中的栈更为通用,可用于解决各种问题。
为什么 JVM 中的栈也被称为"栈"呢?这是因为它在实现上采用了栈的数据结构,具有后进先出的特性,同时也与通常的栈概念有相似之处。这种叫法更多地体现了其实现和操作方式上的一些相似性。
版权声明: 本文为 InfoQ 作者【鲁米】的原创文章。
原文链接:【http://xie.infoq.cn/article/02d5c1eeb6e2417254423f933】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论