文章目录
Stack
是 Java 中实现 后进先出(LIFO, Last In First Out) 数据结构的经典类,位于 java.util
包中。它继承自 Vector
类,利用 Vector
的动态数组特性实现了栈的基本功能。尽管 Stack
在早期 Java 中被广泛使用,但现代开发中已逐渐被更灵活的 Deque
接口(如 ArrayDeque
)取代。以下是 Stack
类的详细介绍:
一、Stack类的核心特性
1. 继承关系
Stack
是Vector
的子类,而Vector
实现了List
接口。Stack
继承了Vector
的所有方法(如add
、get
、remove
),并扩展了栈相关的操作(如push
、pop
)。Vector
是线程安全的,因此Stack
也是线程安全的。
2. 数据结构
- 底层基于数组实现,支持动态扩容。
- 元素从栈顶(数组末尾)进行压入(
push
)和弹出(pop
)操作。
3. 线程安全性
- 所有方法(如
push
、pop
)都通过synchronized
关键字修饰,确保多线程环境下的线程安全。 - 缺点:由于锁粒度较粗(整个栈对象加锁),在高并发场景下性能较低。
4. 已弃用状态
Stack
被标记为 遗留类(@Deprecated
),官方推荐使用Deque
接口及其实现类(如ArrayDeque
)替代。Deque
提供了更灵活的方法(如addFirst
、removeLast
),且性能更优。
二、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
方法(如push
、pop
、peek
)均通过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
)。 - 指定扩容增量:容量增加
capacityIncrement
(newCapacity = 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. 对比表格
特性 | Stack | ArrayDeque |
---|---|---|
线程安全 | ✅ 是(方法级同步) | ❌ 否 |
性能 | 较低(同步锁开销) | 高 |
扩容策略 | 默认翻倍 | 动态扩容(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
的特性、扩容策略和适用场景,有助于在特定需求下合理选择数据结构。