故事:你的“老式弹簧售货机”(Stack<E>)

把 Java 的 Stack 想象成一个老式的 ​​弹簧自动售货机​ ​(就是那种按一下按钮,最上面的商品被推出来的那种)。这篇文章揭秘的就是这台"老式售货机"的内部构造、工作原理,以及为什么现在大家都更喜欢用新型的"旋转寿司传送带"(ArrayDeque)当栈用了!


​故事主角:你的"老式弹簧售货机"(Stack<E>)​

  1. ​机器来历 (继承关系)​

    • 这台售货机 (Stack) 其实是用一个 ​老式伸缩货架 (Vector)​ 改装的。Vector 是 Java 早期的一种动态数组,像一根可以伸缩的弹簧柱塞。
    • Stack extends Vector:这意味着售货机 ​继承​ 了货架的所有功能(放东西、取东西、扩容),并 ​添加​ 了栈特有的操作(只操作最上面那个商品)。
  2. ​核心构造 (内部结构)​

    • ​货架 (Object[] elementData)​ ​: 一个 ​​数组​ ​,实际存放商品(元素)。初始货架能放 ​​10​​ 个商品。

    • ​商品计数器 (int elementCount)​ ​: 记录货架上 ​​实际有多少个​​ 商品。

    • ​扩容机制​ ​: 当货架满了(elementCount == elementData.length),就要 ​​拉长货架弹簧(扩容)​​!

      • 新货架大小 = 旧货架大小 * 2(或者按设定的增量 capacityIncrement 增加)。
      • 把旧商品 ​一个个挪到新货架上​ (System.arraycopy),这比较耗时(O(n))。
  3. ​核心操作 (工作原理 - 后进先出 LIFO)​

    • ​放入商品 (push(E item))​​:

      scss 复制代码
      java
      Copy
      public E push(E item) {
          addElement(item); // 调用父类Vector的方法,把商品放到货架最后面(相当于压到最底下)
          return item;
      }
      // Vector.addElement 简化版
      public synchronized void addElement(E obj) {
          ensureCapacityHelper(elementCount + 1); // 确保货架有位置(不够就扩容)
          elementData[elementCount++] = obj; // 放在计数器位置,计数器+1
      }
      • 想象:你 ​用力一压​ ,新商品被塞进货架最 ​底部​ 。因为货架是数组,新商品实际放在 elementData[elementCount],然后 elementCount++
      • 关键点:虽然叫"压栈",但因为底层是数组,新元素其实放在 ​物理位置上的末尾​(数组索引最大处)。但因为栈只关心"最上面",所以逻辑上它是"栈顶"。
    • ​取出商品 (pop())​​:

      java 复制代码
      java
      Copy
      public synchronized E pop() {
          E obj = peek(); // 看看最上面是什么(不拿走)
          removeElementAt(elementCount - 1); // 调用父类Vector的方法,移除货架最顶上的商品(数组最后一个)
          return obj;
      }
      // Vector.removeElementAt 简化版
      public synchronized void removeElementAt(int index) {
          // ...检查索引...
          modCount++; // 记录结构变化
          int numMoved = elementCount - index - 1;
          if (numMoved > 0) {
              System.arraycopy(elementData, index + 1, elementData, index, numMoved); // 把后面的商品往前挪?等等!这里是移除最后一个,所以不用挪!
          }
          elementData[--elementCount] = null; // 把腾出来的位置清空,商品计数器-1
      }
      • 想象:你按一下按钮,​最顶上​ (货架最后面,索引 elementCount - 1)的商品 ​被弹簧弹出来​
      • 关键点:直接移除数组最后一个元素 (elementData[elementCount - 1]),效率很高 (O(1))。​不需要移动其他商品!​ (System.arraycopy 在移除最后一个元素时 numMoved=0,不会执行)。
    • ​偷看最上面 (peek())​​:

      scss 复制代码
      java
      Copy
      public synchronized E peek() {
          int len = size(); // 获取当前商品数量
          if (len == 0) throw new EmptyStackException(); // 空的?报错!
          return elementAt(len - 1); // 返回货架最后一个商品(栈顶)
      }
      • 想象:隔着玻璃看一眼最上面的商品是啥,但不拿出来。
      • 关键点:直接访问数组最后一个元素 (elementData[elementCount - 1]),效率超高 (O(1))。
  4. ​老式售货机的特点 (Stack 的特性)​

    • ​线程安全 (synchronized)​ : 它的所有关键操作 (push, pop, peek, size, 内部调用的 Vector 方法) 都加了 ​锁 (synchronized)​ 。想象售货机每次只允许一个人操作,其他人要排队。​安全但慢!​ 这是它最大的特点,也是最大的缺点(在单线程下)。
    • ​拒绝空盒子 (null)​ : 你不能放进一个空商品 (null),push(null) 会直接报错 (NullPointerException)。
    • ​查找商品位置 (search(Object o))​ : 告诉机器一个商品的样子,它会从 ​最上面(最后放进去的)开始往下找​ ,告诉你这是第几层(从 ​1​ 开始计数)。找到返回层数,找不到返回 -1
    • ​判断机器是否空 (empty())​ : 看计数器 elementCount 是不是 0
  5. ​为什么大家更喜欢新型"旋转寿司店"(ArrayDeque)当栈用?​

    • ​老式售货机 (Stack) 的缺点​​:

      1. ​慢!​ :因为每个操作都要锁门(synchronized),在单线程下纯属多余开销。
      2. ​历史包袱重​ :继承了 Vector 的所有方法(比如 get(int index), addElement),而这些方法在纯粹的栈操作中通常是不需要的,甚至可能被误用破坏栈的结构。
    • ​新型寿司店 (ArrayDeque) 的优点​​:

      1. ​快!​ :没有锁 (synchronized),单线程下操作头尾 (addFirst/removeFirst 当栈用) 都是极快的 O(1)
      2. ​专注​ :它就是一个纯粹的双端队列 (Deque),用它当栈 (push/pop/peek) 概念更清晰。
      3. ​扩容更聪明​ :虽然也扩容 (O(n)),但基于循环数组的设计通常更高效。
      4. ​空间效率​ :通常比 Stack(继承自 Vector)占用稍少一点内存。
    • ​官方建议​ ​:Java 官方文档明确建议用 Deque 接口及其实现(如 ArrayDeque)替代 Stack 来当栈使用!

  6. ​老式售货机还能用的场景 (Stack 的应用场景)​

    • 虽然效率不高,但原理简单易懂,理解栈概念时用它演示很直观。
    • ​极少数​ 需要保证栈操作 ​线程安全​ 且对性能要求不高 的古老代码或特定场景中。但现在更好的线程安全选择是 ConcurrentLinkedDequeCollections.synchronizedDeque(new ArrayDeque<>())

​总结一下,这篇文章讲的就是:​

  1. Stack 是什么?​ ​ Java 早期提供的、基于 ​​老式动态数组 (Vector)​ ​ 实现的 ​​栈 (LIFO)​​ 数据结构。

  2. ​它怎么工作?​

    • 压栈 (push):塞到数组 ​末尾​ (物理位置),逻辑成为 ​栈顶​
    • 弹栈 (pop):移除并返回数组 ​最后一个元素​ (栈顶)。
    • 查看栈顶 (peek):返回数组 ​最后一个元素​
    • 底层数组 ​动态扩容​ (翻倍或按增量)。
  3. ​核心特点:​

    • ​线程安全 (synchronized)​: 最大特色也是最大性能负担。
    • ​不允许 null
    • 继承 Vector 的方法,可能被误用。
    • 操作栈顶 (pop/peek) 时间复杂度是 O(1) (不扩容时),但受同步锁拖累。
  4. ​主要缺点:​ ​ 同步锁导致 ​​单线程性能差​​。历史包袱重。

  5. ​现代替代品:ArrayDeque ​:更快、更专注、更现代。​​官方推荐用 Deque (ArrayDeque) 替代 Stack!​

  6. ​学习价值:​​ 理解栈的基本原理和基于数组的实现很好。理解"为什么它被弃用"对写出好代码很重要。

​一句话记住 Stack:它就像一个加了安全锁的老式弹簧售货机,原理简单但效率不高,现代开发中更推荐使用灵活高效的 ArrayDeque 来当栈!​ ​ 🧾🔒 下次看到 Stack,知道它是历史遗迹就好,新项目优先考虑 ArrayDeque

相关推荐
whysqwhw30 分钟前
Egloo 中Kotlin 多平台中的 expect/actual
android
用户20187928316733 分钟前
《Android 城堡防御战:ProGuard 骑士的代码混淆魔法》
android
用户2018792831671 小时前
🔐 加密特工行动:Android 中的 AES 与 RSA 秘密行动指南
android
liang_jy2 小时前
Android AIDL 原理
android·面试·源码
用户2018792831672 小时前
Android开发的"魔杖"之ADB命令
android
_荒3 小时前
uniapp AI流式问答对话,问答内容支持图片和视频,支持app和H5
android·前端·vue.js
冰糖葫芦三剑客3 小时前
Android录屏截屏事件监听
android
东风西巷3 小时前
LSPatch:免Root Xposed框架,解锁无限可能
android·生活·软件需求
用户2018792831674 小时前
图书馆书架管理员的魔法:TreeMap 的奇幻之旅
android
androidwork4 小时前
Kotlin实现文件上传进度监听:RequestBody封装详解
android·开发语言·kotlin