【Java第86集】java Stack类详解

Stack 是 Java 中实现 后进先出(LIFO, Last In First Out) 数据结构的经典类,位于 java.util 包中。它继承自 Vector 类,利用 Vector 的动态数组特性实现了栈的基本功能。尽管 Stack 在早期 Java 中被广泛使用,但现代开发中已逐渐被更灵活的 Deque 接口(如 ArrayDeque)取代。以下是 Stack 类的详细介绍:

一、Stack类的核心特性

1. 继承关系

  • StackVector 的子类,而 Vector 实现了 List 接口。
  • Stack 继承了 Vector 的所有方法(如 addgetremove),并扩展了栈相关的操作(如 pushpop)。
  • Vector 是线程安全的,因此 Stack 也是线程安全的。

2. 数据结构

  • 底层基于数组实现,支持动态扩容。
  • 元素从栈顶(数组末尾)进行压入(push)和弹出(pop)操作。

3. 线程安全性

  • 所有方法(如 pushpop)都通过 synchronized 关键字修饰,确保多线程环境下的线程安全。
  • 缺点:由于锁粒度较粗(整个栈对象加锁),在高并发场景下性能较低。

4. 已弃用状态

  • Stack 被标记为 遗留类@Deprecated),官方推荐使用 Deque 接口及其实现类(如 ArrayDeque)替代。
  • Deque 提供了更灵活的方法(如 addFirstremoveLast),且性能更优。

二、Stack的构造函数

Stack 只有一个默认构造函数:

public Stack()
  • 创建一个空的 Stack,初始容量为 10(继承自 Vector)。
  • 示例:
    Stack<String> stack = new Stack<>();
    

三、Stack的核心方法

1. 栈的基本操作

方法描述
push(E item)将元素压入栈顶。
pop()弹出栈顶元素并返回。若栈为空,抛出 EmptyStackException
peek()查看栈顶元素但不弹出。若栈为空,抛出 EmptyStackException
empty()判断栈是否为空。
search(Object o)返回元素在栈中的位置(从栈顶开始计数,1 为基数)。若未找到,返回 -1。

示例代码

Stack<Integer> stack = new Stack<>();
stack.push(10); // 压入元素
stack.push(20);
stack.push(30);

System.out.println("栈顶元素: " + stack.peek()); // 输出 30
System.out.println("弹出元素: " + stack.pop());   // 输出 30,栈变为 [10, 20]
System.out.println("栈是否为空: " + stack.empty()); // 输出 false
System.out.println("元素20的位置: " + stack.search(20)); // 输出 2

2. 继承自Vector的方法

  • size():获取栈中元素数量。
  • get(int index):获取指定索引的元素(从栈底到栈顶按 0 开始计数)。
  • removeElementAt(int index):移除指定索引的元素。
  • addElement(E obj):与 push 功能相同。

四、Stack的线程安全机制

1. 同步方法

  • 所有 Stack 方法(如 pushpoppeek)均通过 synchronized 关键字修饰,确保同一时刻只有一个线程访问栈。
  • 示例:
    public synchronized E pop() {
        E obj;
        int len = size();
    
        obj = peek();
        removeElementAt(len - 1);
        return obj;
    }
    

2. 优点

  • 简单易用,无需手动同步。
  • 多线程环境下无需额外加锁。

3. 缺点

  • 性能开销:每次方法调用都需要获取锁,高并发下性能较低。
  • 锁粒度粗:整个栈对象被加锁,即使只操作单个元素,也会阻塞其他线程。

五、Stack的扩容机制

  • Stack 的底层是 Vector,因此其扩容策略与 Vector 一致:
    • 默认扩容:容量翻倍(newCapacity = oldCapacity * 2)。
    • 指定扩容增量:容量增加 capacityIncrementnewCapacity = oldCapacity + capacityIncrement)。
  • 示例:
    Stack<Integer> stack = new Stack<>();
    for (int i = 0; i < 20; i++) {
        stack.push(i);
    }
    System.out.println("当前容量: " + stack.capacity()); // 初始容量为10,最终扩容为34
    

六、Stack与Deque的对比

1. 推荐替代方案

  • Deque(双端队列)接口的实现类(如 ArrayDeque)提供了更高效的栈操作。
  • 示例:
    Deque<Integer> stack = new ArrayDeque<>();
    stack.push(10); // 压栈
    stack.pop();    // 弹栈
    

2. 对比表格

特性StackArrayDeque
线程安全✅ 是(方法级同步)❌ 否
性能较低(同步锁开销)
扩容策略默认翻倍动态扩容(1.5 倍)
适用场景多线程环境(需谨慎使用)单线程或需手动同步的场景
推荐使用❌ 不推荐✅ 推荐

七、Stack的使用注意事项

1. 快速失败的迭代器

  • 使用 Iterator 遍历时,若在迭代过程中修改 Stack,会抛出 ConcurrentModificationException
  • 解决方案:使用 Enumeration 或在遍历时对 Stack 加锁。

2. 内存开销

  • 扩容可能导致内存浪费(如频繁扩容时)。
  • 建议根据业务需求合理设置初始容量和容量增量。

3. 替代方案

  • 高并发场景:使用 ConcurrentLinkedDeque(非阻塞线程安全队列)。
  • 单线程场景:使用 ArrayDeque(性能更高)。
  • 手动同步:使用 Collections.synchronizedDeque(new ArrayDeque<>())

八、Stack的应用场景

1. 推荐使用场景

  • 小规模多线程环境(如连接池、缓存)。
  • 需要简单线程安全的集合,且性能要求不高。

2. 典型用例

  • 括号匹配验证

    public static boolean isValid(String s) {
        Deque<Character> stack = new ArrayDeque<>();
        for (char c : s.toCharArray()) {
            if (c == '(' || c == '[' || c == '{') {
                stack.push(c);
            } else {
                if (stack.isEmpty()) return false;
                char top = stack.pop();
                if ((c == ')' && top != '(') || (c == ']' && top != '[') || (c == '}' && top != '{')) {
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }
    
  • 递归调用模拟

    Stack<Integer> stack = new Stack<>();
    stack.push(1);
    stack.push(2);
    stack.push(3);
    while (!stack.empty()) {
        System.out.println(stack.pop()); // 输出 3, 2, 1
    }
    

九、总结

Stack 是 Java 中实现后进先出数据结构的遗留类,尽管其线程安全特性简化了多线程开发,但性能开销较大且已被官方标记为弃用。现代开发中更推荐使用 Deque 接口及其实现类(如 ArrayDeque),它们提供了更高效的操作和更灵活的功能。理解 Stack 的特性、扩容策略和适用场景,有助于在特定需求下合理选择数据结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值