集合-Stack&Queue源码
大约 4 分钟
集合-Stack&Queue源码
一、核理论
1.1 Stack与Queue类结构分析
Stack是基于Vector实现的后进先出(LIFO)数据结构,而Queue接口则提供了先进先出(FIFO)的规范,主要实现类包括ArrayDeque、LinkedList等。
1.2 核心成员变量
- Stack: 继承Vector,底层使用数组存储元素
- ArrayDeque: 使用循环数组实现,维护head和tail指针
- LinkedList: 基于双向链表实现,维护first和last指针
1.3 JDK版本特性差异
- JDK 1.0: 引入Stack类
- JDK 1.5: 引入Queue接口及LinkedList实现
- JDK 1.6: 引入ArrayDeque,提供更高效的双端队列实现
- JDK 21: 未对Stack和Queue接口做重大修改,但ArrayDeque性能持续优化
二、代码实践
2.1 Stack核心方法实现
/**
 * 栈的入栈操作
 * @param item 要入栈的元素
 * @return 入栈的元素
 */
public E push(E item) {
    addElement(item);
    return item;
}
/**
 * 栈的出栈操作
 * @return 出栈的元素
 * @throws EmptyStackException 如果栈为空
 */
public synchronized E pop() {
    E       obj;
    int     len = size();
    obj = peek();
    removeElementAt(len - 1);
    return obj;
}2.2 ArrayDeque核心方法实现
/**
 * 双端队列的尾部添加元素
 * @param e 要添加的元素
 * @return true (始终返回true)
 * @throws NullPointerException 如果指定元素为null
 */
public boolean add(E e) {
    addLast(e);
    return true;
}
/**
 * 双端队列的头部移除并返回元素
 * @return 头部元素,如果队列为空则返回null
 */
public E poll() {
    return pollFirst();
}2.3 实际应用示例
2.3.1 使用Stack实现括号匹配
/**
 * 检查括号是否匹配
 * @param s 输入字符串
 * @return 如果括号匹配则返回true,否则返回false
 */
public boolean isValid(String s) {
    Stack<Character> stack = new Stack<>();
    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();
}2.3.2 使用Queue实现生产者-消费者模型
/**
 * 生产者-消费者模型示例
 */
public class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int CAPACITY = 5;
    private int count = 0;
    /**
     * 生产者线程
     */
    public void produce() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (queue.size() == CAPACITY) {
                    wait();
                }
                System.out.println("生产者生产: " + count);
                queue.add(count++);
                notifyAll();
                Thread.sleep(1000);
            }
        }
    }
    /**
     * 消费者线程
     */
    public void consume() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (queue.isEmpty()) {
                    wait();
                }
                int val = queue.poll();
                System.out.println("消费者消费: " + val);
                notifyAll();
                Thread.sleep(1000);
            }
        }
    }
}三、设计思想
3.1 Stack的设计缺陷
Stack继承自Vector,导致其所有方法都是同步的,性能较差。同时,继承Vector暴露了过多的公共方法,破坏了栈的封装性。
3.2 ArrayDeque的高效设计
ArrayDeque使用循环数组实现,避免了LinkedList的节点创建开销,同时通过head和tail指针实现高效的双端操作。
3.3 接口与实现分离原则
Queue接口定义了队列的规范,而具体实现由LinkedList、ArrayDeque等类提供,体现了面向接口编程的思想。
四、避坑指南
4.1 Stack的线程安全问题
虽然Stack是线程安全的,但性能较差,在单线程环境下建议使用ArrayDeque替代。
4.2 Queue的add()与offer()区别
- add(): 当队列满时抛出IllegalStateException
- offer(): 当队列满时返回false 建议使用offer()方法并检查返回值,避免异常抛出。
4.3 ArrayDeque的null元素问题
ArrayDeque不允许添加null元素,否则会抛出NullPointerException,而LinkedList允许添加null元素。
五、深度思考题
- 为什么Java官方推荐使用Deque接口及其实现类替代Stack类?
- ArrayDeque和LinkedList作为Queue和Deque的实现,各自的性能特点是什么?
- 如何实现一个线程安全的非阻塞队列?
- JDK中的PriorityQueue是如何实现的?它与普通Queue有何区别?
思考题回答:
- Java官方推荐使用Deque接口替代Stack类的主要原因是: - Stack继承自Vector,所有方法都是同步的,性能较差
- Stack暴露了过多的公共方法,破坏了栈的封装性
- Deque接口提供了更完整的双端队列操作,同时ArrayDeque实现性能更优
 
- ArrayDeque和LinkedList的性能特点对比: - ArrayDeque基于数组实现,随机访问性能更好,添加删除元素时可能需要扩容
- LinkedList基于链表实现,添加删除元素不需要移动其他元素,但随机访问性能较差
- 对于队列操作,ArrayDeque通常比LinkedList性能更好,因为它避免了节点创建和回收的开销
 
