Java中栈是一种常见的数据结构,遵循后进先出的操作规则,常用于表达式求值、方法调用栈等场景。传统的Stack类是早期Java集合框架中的实现,而ArrayDeque作为双端队列也可以作为栈使用,且在实际性能表现上更优。

ArrayDeque作为栈的基本用法
ArrayDeque实现了Deque接口,通过特定的方法调用就可以实现栈的核心操作,常用的方法对应关系如下:
- 入栈:使用
push(E e)方法,将元素添加到队列头部 - 出栈:使用
pop()方法,移除并返回队列头部的元素 - 查看栈顶:使用
peek()方法,返回队列头部的元素但不移除 - 判断栈是否为空:使用
isEmpty()方法
以下是一个简单的使用示例:
import java.util.ArrayDeque;
import java.util.Deque;
public class ArrayDequeStackDemo {
public static void main(String[] args) {
// 创建ArrayDeque作为栈使用
Deque<Integer> stack = new ArrayDeque<>();
// 入栈操作
stack.push(1);
stack.push(2);
stack.push(3);
// 查看栈顶元素
System.out.println("栈顶元素:" + stack.peek()); // 输出3
// 出栈操作
System.out.println("出栈元素:" + stack.pop()); // 输出3
System.out.println("出栈元素:" + stack.pop()); // 输出2
// 判断栈是否为空
System.out.println("栈是否为空:" + stack.isEmpty()); // 输出false
}
}
Stack类的实现特点
Stack类继承自Vector类,Vector本身是一个线程安全的可变数组实现,它的所有修改操作都通过synchronized关键字加锁,保证多线程环境下的操作安全。Stack类在Vector的基础上扩展了栈的相关方法,比如push、pop、peek等,本质上都是调用Vector的父类方法实现。
以下是一个Stack类的简单使用示例:
import java.util.Stack;
public class StackDemo {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
// 入栈
stack.push(1);
stack.push(2);
stack.push(3);
// 查看栈顶
System.out.println("栈顶元素:" + stack.peek()); // 输出3
// 出栈
System.out.println("出栈元素:" + stack.pop()); // 输出3
System.out.println("出栈元素:" + stack.pop()); // 输出2
}
}
ArrayDeque性能更优的核心原因
1. 同步机制带来的开销差异
Stack类的所有方法都继承了Vector的synchronized加锁机制,即使是在单线程环境下使用,每次方法调用都需要进行锁的获取和释放操作,这部分开销在频繁操作时会被放大。而ArrayDeque没有实现任何同步机制,所有操作都没有加锁开销,在单线程场景下性能自然更高。
如果需要在多线程环境下使用栈,开发者可以自行选择加锁方式或者使用Collections.synchronizedDeque包装ArrayDeque,比直接使用Stack类更灵活,也能避免不必要的同步开销。
2. 底层数据结构的设计差异
Stack类基于Vector实现,Vector的底层是连续的可变数组,当数组容量不足时,会按照原容量的2倍进行扩容(默认情况下),扩容时需要创建新的数组并将原有数组的元素全部拷贝到新数组中,这个拷贝操作的时间复杂度是O(n)。
ArrayDeque的底层也是可变数组,但它的扩容策略更合理,默认初始容量是16,扩容时是按2的幂次进行扩容,且数组是循环使用的,头尾都可以进行元素的添加和移除,不需要像Vector那样在扩容时频繁移动大量元素。作为栈使用时,ArrayDeque只在头部操作元素,扩容时的拷贝开销相对更低。
3. 继承结构带来的设计问题
Stack类继承自Vector,这意味着Stack类可以调用Vector的所有公开方法,比如add(int index, E element)、remove(int index)等,这些方法可以随意在栈的中间位置插入或删除元素,破坏了栈的后进先出特性,属于设计上的缺陷。而ArrayDeque作为双端队列,虽然也可以操作两端元素,但作为栈使用时只需要调用push、pop等指定方法,不会被强制暴露不符合栈特性的操作。
4. 方法调用的链路开销
Stack类的push方法实际上是调用父类的addElement方法,pop方法调用removeElementAt方法,中间经过多层方法调用,而ArrayDeque的push和pop方法是自身直接实现的,方法调用链路更短,执行效率更高。
两者的性能对比测试
我们可以通过简单的测试代码对比两者在入栈和出栈操作上的耗时差异:
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Stack;
public class StackPerformanceTest {
private static final int OPERATION_COUNT = 10000000;
public static void main(String[] args) {
// 测试Stack性能
Stack<Integer> stack = new Stack<>();
long startTime = System.currentTimeMillis();
for (int i = 0; i < OPERATION_COUNT; i++) {
stack.push(i);
stack.pop();
}
long stackTime = System.currentTimeMillis() - startTime;
System.out.println("Stack操作耗时:" + stackTime + "毫秒");
// 测试ArrayDeque性能
Deque<Integer> arrayDeque = new ArrayDeque<>();
startTime = System.currentTimeMillis();
for (int i = 0; i < OPERATION_COUNT; i++) {
arrayDeque.push(i);
arrayDeque.pop();
}
long arrayDequeTime = System.currentTimeMillis() - startTime;
System.out.println("ArrayDeque操作耗时:" + arrayDequeTime + "毫秒");
}
}
多次运行该测试代码会发现,ArrayDeque的耗时通常只有Stack的30%到50%,性能优势非常明显。
使用建议
在Java开发中,如果需要使用栈结构,优先选择ArrayDeque作为实现,除非明确要求使用遗留的Stack类兼容旧代码。如果是单线程场景直接使用ArrayDeque即可,如果是多线程场景,可以通过Collections.synchronizedDeque(new ArrayDeque<>())包装后使用,既保证了线程安全,也保留了更好的性能。
ArrayDequeStack栈Java集合修改时间:2026-07-07 13:39:32