JAVA数据结构与算法 - 基础:队列 (Queue) 全方位解析

数据结构与算法 - 基础:队列 (Queue) 全方位解析

一、队列------排队的哲学

如果说栈是"后来者居上",那么队列就是"先来者先走"------先进入队伍的人先被服务,后进入的人在后面等待。这种 FIFO(First In, First Out,先进先出) 的规则,构成了现实世界中最公平的服务模型。

从银行柜台排队、操作系统打印任务的调度,到消息中间件(Kafka、RabbitMQ)的消息传递,再到 Java 线程池的任务缓冲------队列的身影无处不在。

scss 复制代码
入队(enqueue) ──→ [8] [3] [5] [1] [9] ──→ 出队(dequeue)
                   ↑                ↑
                 队尾(rear)      队头(front)

栈和队列的核心差异在操作方向上:

特性 栈 (Stack) 队列 (Queue)
操作约束 仅一端操作(栈顶) 两端各一种操作
入操作 push(同一端) offer / add(队尾)
出操作 pop(同一端) poll / remove(队头)
查看操作 peek(同一端) peek(队头)
顺序语义 LIFO(后进先出) FIFO(先进先出)
心理比喻 一摞盘子 排队买票

二、循环数组队列------用数组高效模拟环形结构

2.1 循环队列的设计思想

如果用普通数组实现队列而不做任何优化,会出现一个严重的问题:当队头的元素被移除后,前面的空间就"浪费"了,队尾不断向后移动,最终到达数组末尾就无法再入队------这就是"假溢出"。

循环队列巧妙地解决了这个问题:将数组视为一个首尾相连的环。当队尾指针到达数组末尾时,下一个位置"绕回"到数组的起始位置(下标 0)。

ini 复制代码
普通队列的假溢出:
  下标: [0]  [1]  [2]  [3]  [4]
  状态:  空   空   空   C    D
                     ↑         ↑
                   front     rear (到末尾了,无法再入队!)

循环队列:
  下标: [0]  [1]  [2]  [3]  [4]
  状态:  E    F   空   C    D
         ↑              ↑
        rear          front
  (rear 绕回到 index 0,空间得到复用)

2.2 完整实现

java 复制代码
import java.util.NoSuchElementException;

public class CircularArrayQueue<E> {

    private Object[] data;        // 底层循环数组
    private int front;            // 队头指针(指向第一个有效元素)
    private int rear;             // 队尾指针(指向最后一个有效元素的下一个位置)
    private int size;             // 当前元素个数
    private static final int DEFAULT_CAPACITY = 8;

    public CircularArrayQueue() {
        data = new Object[DEFAULT_CAPACITY];
        front = 0;
        rear = 0;
        size = 0;
    }

    public CircularArrayQueue(int capacity) {
        data = new Object[Math.max(1, capacity)];
        front = 0;
        rear = 0;
        size = 0;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean isFull() {
        return size == data.length;
    }

    /**
     * 入队------将元素添加到队尾
     * 时间复杂度: 均摊 O(1),扩容时 O(n)
     * 空间复杂度: O(1)(扩容除外)
     */
    public boolean offer(E item) {
        if (isFull()) {
            resize(data.length * 2);
        }
        data[rear] = item;
        rear = (rear + 1) % data.length; // 核心:取模实现环形
        size++;
        return true;
    }

    /**
     * 出队------移除并返回队头元素
     * 时间复杂度: O(1)
     * 空间复杂度: O(1)
     * @throws NoSuchElementException 队列为空时抛出
     */
    @SuppressWarnings("unchecked")
    public E poll() {
        if (isEmpty()) {
            throw new NoSuchElementException("队列为空,无法出队");
        }
        E item = (E) data[front];
        data[front] = null;               // 帮助 GC
        front = (front + 1) % data.length; // 核心:队头环绕前进
        size--;
        return item;
    }

    /**
     * 查看队头元素(不移除)
     */
    @SuppressWarnings("unchecked")
    public E peek() {
        if (isEmpty()) {
            return null;
        }
        return (E) data[front];
    }

    /**
     * 扩容:需要将循环数组"展开"成从 0 开始的线性排列
     */
    private void resize(int newCapacity) {
        Object[] newData = new Object[newCapacity];
        // 从 front 开始,顺序拷贝 size 个元素到新数组的 [0..size-1]
        for (int i = 0; i < size; i++) {
            newData[i] = data[(front + i) % data.length];
        }
        data = newData;
        front = 0;
        rear = size; // 新数组中所有元素在 [0, size-1]
    }

    @Override
    public String toString() {
        if (isEmpty()) return "[] (空队列)";
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < size; i++) {
            sb.append(data[(front + i) % data.length]);
            if (i < size - 1) sb.append(", ");
        }
        sb.append("]");
        return sb.toString();
    }

    public String debugInfo() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("front=%d, rear=%d, size=%d/%d\n", front, rear, size, data.length));
        sb.append("数组内容: [");
        for (int i = 0; i < data.length; i++) {
            sb.append(data[i] == null ? "_" : data[i]);
            if (i < data.length - 1) sb.append(", ");
        }
        sb.append("]");
        return sb.toString();
    }

    // ========== 测试主方法 ==========
    public static void main(String[] args) {
        System.out.println("========== 循环数组队列------完整演示 ==========\n");

        CircularArrayQueue<Integer> queue = new CircularArrayQueue<>(5);

        System.out.println("--- 入队(enqueue) ---");
        for (int i = 1; i <= 8; i++) {
            queue.offer(i * 10);
            System.out.printf("入队 %2d → %s\n", i * 10, queue);
            System.out.println("  " + queue.debugInfo());
        }
        System.out.println("(触发了一次扩容:5 → 10)");

        System.out.println("\n--- 出队(dequeue) ---");
        for (int i = 1; i <= 4; i++) {
            int val = queue.poll();
            System.out.printf("出队 %2d → %s\n", val, queue);
            System.out.println("  " + queue.debugInfo());
        }

        System.out.println("\n--- 再次入队(验证循环复用) ---");
        for (int i = 1; i <= 4; i++) {
            queue.offer(i * 100);
            System.out.printf("入队 %3d → %s\n", i * 100, queue);
            System.out.println("  " + queue.debugInfo());
        }

        System.out.println("\n--- 清空队列 ---");
        while (!queue.isEmpty()) {
            System.out.printf("出队 %-3d → %s\n", queue.poll(), queue);
        }
    }
}

三、链式队列------无限容量的队列

链表实现队列不存在容量限制,也不需要扩容时的数组拷贝。核心思路是维护两个指针------队头指针 head(用于出队)和队尾指针 tail(用于入队)。

java 复制代码
import java.util.NoSuchElementException;

public class LinkedQueue<E> {

    /** 内部节点 */
    private static class Node<E> {
        E data;
        Node<E> next;

        Node(E data) {
            this.data = data;
            this.next = null;
        }
    }

    private Node<E> head;   // 队头(最早进入的元素)
    private Node<E> tail;   // 队尾(最新进入的元素)
    private int size;       // 元素个数

    public LinkedQueue() {
        head = null;
        tail = null;
        size = 0;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 入队:在链表尾部追加节点
     * 时间复杂度: 严格 O(1)
     *
     * 图示 (enqueue "C"):
     *   head → [A] → [B] → null
     *                        ↑ tail
     *   插入后:
     *   head → [A] → [B] → [C] → null
     *                               ↑ tail
     */
    public boolean offer(E item) {
        Node<E> newNode = new Node<>(item);
        if (isEmpty()) {
            head = newNode;
            tail = newNode;
        } else {
            tail.next = newNode;  // 当前的 tail 后继指向新节点
            tail = newNode;       // tail 移动到新节点
        }
        size++;
        return true;
    }

    /**
     * 出队:移除链表头部节点
     * 时间复杂度: 严格 O(1)
     */
    public E poll() {
        if (isEmpty()) {
            return null;
        }
        E item = head.data;
        head = head.next;
        size--;
        // 如果移除后队列为空,tail 也应置 null
        if (isEmpty()) {
            tail = null;
        }
        return item;
    }

    /**
     * 查看队头
     */
    public E peek() {
        return isEmpty() ? null : head.data;
    }

    @Override
    public String toString() {
        if (isEmpty()) return "(空队列)";
        StringBuilder sb = new StringBuilder("front → ");
        Node<E> current = head;
        while (current != null) {
            sb.append("[").append(current.data).append("]");
            if (current.next != null) sb.append(" → ");
            current = current.next;
        }
        sb.append(" ← rear");
        return sb.toString();
    }

    // ========== 测试主方法 ==========
    public static void main(String[] args) {
        System.out.println("========== 链式队列------完整演示 ==========\n");

        LinkedQueue<String> queue = new LinkedQueue<>();

        String[] tasks = {"编译源码", "运行测试", "打包部署", "发送通知", "清理缓存"};
        for (String task : tasks) {
            queue.offer(task);
            System.out.println("入队: " + task);
        }
        System.out.println("队列: " + queue);
        System.out.println("当前大小: " + queue.size());

        System.out.println("\n--- 按顺序处理任务 ---");
        while (!queue.isEmpty()) {
            String task = queue.poll();
            System.out.printf("正在处理: %-10s → 剩余: %s\n", task, queue);
        }
    }
}

四、双端队列 (Deque)------两端都能操作

java.util.Deque 是 Queue 的一个强大扩展:它打破了"一端进一端出"的限制,允许在两端都进行插入和删除。这使得它既能当队列用(FIFO),又能当栈用(LIFO)。

java 复制代码
import java.util.ArrayDeque;
import java.util.Deque;

public class DequeDemo {

    public static void main(String[] args) {
        System.out.println("========== 双端队列 (Deque) 全功能演示 ==========\n");

        Deque<Integer> deque = new ArrayDeque<>();

        // ======== 队列模式:尾部入、头部出 ========
        System.out.println("--- 队列模式 (FIFO) ---");
        deque.offerLast(10);
        deque.offerLast(20);
        deque.offerLast(30);
        System.out.println("入队顺序: 10, 20, 30");
        while (!deque.isEmpty()) {
            System.out.println("  出队: " + deque.pollFirst());
        }

        // ======== 栈模式:头部入、头部出 ========
        System.out.println("\n--- 栈模式 (LIFO) ---");
        deque.push(100);            // 等价于 addFirst
        deque.push(200);
        deque.push(300);
        System.out.println("入栈顺序: 100, 200, 300");
        while (!deque.isEmpty()) {
            System.out.println("  出栈: " + deque.pop());  // 等价于 removeFirst
        }

        // ======== 双端操作 ========
        System.out.println("\n--- 双端灵活操作 ---");
        deque.addFirst(1);     // 头部加 1
        deque.addLast(5);      // 尾部加 5
        deque.addFirst(0);     // 头部加 0
        deque.addLast(9);      // 尾部加 9
        System.out.println("操作后: " + deque + "  (大小=" + deque.size() + ")");

        System.out.println("队头: " + deque.peekFirst());
        System.out.println("队尾: " + deque.peekLast());
        System.out.println("移除队头: " + deque.pollFirst());
        System.out.println("移除队尾: " + deque.pollLast());
        System.out.println("最终: " + deque);
    }
}

五、优先队列------不按先来后到,按优先级

5.1 核心概念

普通队列是严格的 FIFO:先到先服务。但现实中有大量场景不适用这个规则------医院的急诊优先、VIP 用户的请求优先、操作系统中高优先级进程优先获得 CPU。

优先队列 的出队顺序不再取决于入队顺序,而是取决于元素的优先级 。底层通常使用二叉堆(Binary Heap) 实现,保证了插入和删除操作都是 O(log n)。

在 Java 中,java.util.PriorityQueue 默认是最小堆(堆顶元素最小),可以通过传入 Comparator 自定义排序规则。

5.2 完整实现

java 复制代码
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Random;

public class PriorityQueueDemo {

    /** 任务类:包含名称和优先级 */
    static class Task {
        String name;
        int priority; // 数字越小,优先级越高

        Task(String name, int priority) {
            this.name = name;
            this.priority = priority;
        }

        @Override
        public String toString() {
            return "[" + name + " p=" + priority + "]";
        }
    }

    public static void main(String[] args) {
        System.out.println("========== 优先队列------完整演示 ==========\n");

        // 最小堆:priority 越小越靠近堆顶 → 越优先出队
        PriorityQueue<Task> pq = new PriorityQueue<>(
                Comparator.comparingInt(t -> t.priority)
        );

        System.out.println("--- 插入任务(乱序插入,按优先级出队) ---");
        Task[] tasks = {
                new Task("编译项目", 3),
                new Task("紧急修复Bug", 1),
                new Task("代码Review", 2),
                new Task("写文档", 5),
                new Task("合并代码", 2),
                new Task("发布上线", 1),
                new Task("清理日志", 4),
        };

        for (Task t : tasks) {
            pq.offer(t);
            System.out.printf("  插入: %-14s → 当前队首: %s\n",
                    t, pq.peek());
        }

        System.out.println("\n--- 按优先级处理任务 ---");
        while (!pq.isEmpty()) {
            Task next = pq.poll();
            System.out.printf("  处理: %s\n", next);
        }
        System.out.println("(优先级1的先处理,优先级相同的按堆内部顺序出队)");

        // 演示数字优先队列
        System.out.println("\n--- 整数优先队列(最小堆) ---");
        PriorityQueue<Integer> intPQ = new PriorityQueue<>();
        int[] nums = {7, 2, 9, 1, 5, 3, 8};
        for (int n : nums) {
            intPQ.offer(n);
        }
        System.out.print("出队顺序: ");
        while (!intPQ.isEmpty()) {
            System.out.print(intPQ.poll() + " ");
        }
        System.out.println("(自动升序)");

        // 最大堆演示
        System.out.println("\n--- 整数优先队列(最大堆) ---");
        PriorityQueue<Integer> maxPQ = new PriorityQueue<>(Comparator.reverseOrder());
        for (int n : nums) {
            maxPQ.offer(n);
        }
        System.out.print("出队顺序: ");
        while (!maxPQ.isEmpty()) {
            System.out.print(maxPQ.poll() + " ");
        }
        System.out.println("(自动降序)");
    }
}

六、队列在生产环境中的应用模拟

6.1 简易线程池中的任务队列

线程池的核心原理是:一个生产者(提交任务)和多个消费者(工作线程)通过一个共享队列进行解耦。任务是生产者提交的,由消费者------工作线程------按 FIFO 顺序取出执行。

java 复制代码
import java.util.ArrayDeque;
import java.util.Deque;

public class SimpleThreadPoolDemo {

    /** 模拟一个简单的线程池 */
    static class SimpleThreadPool {
        private final Deque<Runnable> taskQueue = new ArrayDeque<>();
        private final WorkerThread[] workers;
        private volatile boolean shutdown = false;

        SimpleThreadPool(int threadCount) {
            workers = new WorkerThread[threadCount];
            for (int i = 0; i < threadCount; i++) {
                workers[i] = new WorkerThread("工作线程-" + (i + 1));
                workers[i].start();
            }
        }

        /** 提交任务到队列------生产者 */
        public void submit(Runnable task) {
            synchronized (taskQueue) {
                if (!shutdown) {
                    taskQueue.offerLast(task);
                    taskQueue.notify();  // 唤醒一个等待的消费者线程
                }
            }
        }

        /** 工作线程从队列取任务------消费者 */
        private class WorkerThread extends Thread {
            WorkerThread(String name) {
                super(name);
            }

            @Override
            public void run() {
                while (!shutdown || !taskQueue.isEmpty()) {
                    Runnable task;
                    synchronized (taskQueue) {
                        while (taskQueue.isEmpty() && !shutdown) {
                            try {
                                taskQueue.wait(); // 队列空时等待
                            } catch (InterruptedException e) {
                                Thread.currentThread().interrupt();
                                return;
                            }
                        }
                        if (taskQueue.isEmpty()) break;
                        task = taskQueue.pollFirst(); // FIFO:取队头任务
                    }
                    task.run(); // 执行任务
                }
            }
        }

        void shutdown() {
            shutdown = true;
            synchronized (taskQueue) {
                taskQueue.notifyAll(); // 唤醒所有等待线程以退出
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== 线程池任务队列演示 ==========\n");

        SimpleThreadPool pool = new SimpleThreadPool(3);  // 3个工作线程

        System.out.println("--- 提交 8 个任务 ---");
        for (int i = 1; i <= 8; i++) {
            final int taskId = i;
            pool.submit(() -> {
                System.out.printf("  [%s] 正在执行任务 #%d\n",
                        Thread.currentThread().getName(), taskId);
                try {
                    Thread.sleep(100); // 模拟任务耗时
                } catch (InterruptedException ignored) {}
            });
            System.out.printf("  提交任务 #%d 到队列\n", taskId);
        }

        Thread.sleep(1500); // 等待所有任务完成
        pool.shutdown();
        System.out.println("\n所有任务执行完毕,线程池关闭。");
    }
}

6.2 使用 JDK 阻塞队列实现生产者-消费者

Java 的 BlockingQueue 自带阻塞等待功能,省去了手动 wait/notify 的代码。

java 复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class ProducerConsumerDemo {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== 生产者-消费者模型 (BlockingQueue) ==========\n");

        // 容量为 5 的有界阻塞队列
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);

        // 生产者线程
        Thread producer = new Thread(() -> {
            String[] items = {"苹果", "香蕉", "橘子", "葡萄", "西瓜", "草莓", "芒果", "桃子"};
            try {
                for (String item : items) {
                    queue.put(item);  // 队列满时阻塞等待
                    System.out.printf("  生产者 → [%s] (队列大小=%d)\n", item, queue.size());
                    TimeUnit.MILLISECONDS.sleep(200);  // 模拟生产速度
                }
                queue.put("EOF"); // 终止标记
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "生产者");

        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    String item = queue.take();  // 队列空时阻塞等待
                    if ("EOF".equals(item)) break;
                    System.out.printf("  消费者 ← [%s] (队列大小=%d)\n", item, queue.size());
                    TimeUnit.MILLISECONDS.sleep(500);  // 模拟消费速度(比生产慢)
                }
                System.out.println("\n消费者收到 EOF,处理完成。");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "消费者");

        producer.start();
        consumer.start();

        producer.join();
        consumer.join();

        System.out.println("(消费速度 < 生产速度,队列会逐渐填满并阻塞生产者)");
    }
}

七、JDK 队列生态全景图

Java 为队列提供了丰富的实现,覆盖了有界/无界、阻塞/非阻塞、单端/双端、优先等多种维度:

实现类 底层结构 边界 阻塞 特性
ArrayDeque 循环数组 可扩展 双端,栈+队列首选
LinkedList 双向链表 无界 支持 null 元素
PriorityQueue 二叉小顶堆 可扩展 按优先级出队
ArrayBlockingQueue 循环数组 有界 单锁实现,支持公平策略
LinkedBlockingQueue 单向链表 可设界 双锁实现(putLock + takeLock),吞吐更高
PriorityBlockingQueue 二叉堆 可扩展 并发优先队列
DelayQueue 优先队列 可扩展 元素须到期后才能取出
SynchronousQueue 无存储 无容量 每个 put 必须等一个 take,直接传递
ConcurrentLinkedQueue 单向链表 无界 CAS 实现,非阻塞并发队列
LinkedTransferQueue 单向链表 无界 支持 transfer------生产者等待消费者直接取走

八、队列的常见应用场景速查

场景 推荐队列 理由
线程池任务缓冲 LinkedBlockingQueue / ArrayBlockingQueue 有界可控,阻塞语义天然适配生产者-消费者
消息中间件 持久化队列(磁盘 + 内存) 支持消息堆积和重启恢复
BFS 广度优先搜索 ArrayDeque / LinkedList 简单的 FIFO 需求
操作系统进程调度 PriorityQueue 高优先级进程优先获得 CPU
延迟任务(定时重试) DelayQueue 元素需延迟到期后才消费
双端操作(LRU 缓存) ArrayDeque 头尾两端都可能操作
Redis 的 List 双端队列 LPUSH/RPOP 实现消息队列

九、总结

队列是"公平"的数据结构------先来者先服务。它从最简单的 FIFO 语义出发,通过不同的变体,满足了现代软件系统中各类复杂的场景需求:

  1. 循环数组队列:空间高效,适用于容量可预估的场景
  2. 链式队列:无需关心容量上限,内存可动态增长
  3. 双端队列:灵活性最强,一端一规则,两端皆可为
  4. 优先队列:打破了时间公平,引入优先级维度
  5. 阻塞队列:多线程并发场景的基石,天然的生产者-消费者缓冲

理解队列,是理解"解耦"与"缓冲"两大架构思想的起点。消息队列解耦了服务的生产和消费速率;线程池的任务队列解耦了任务提交与任务执行的时机。在这些场景中,队列所扮演的角色远不只是"一个装数据的容器"------它是整个系统的流量调节器。

栈和队列联手构成了线性数据结构的双璧。当世界需要"后来者居上"时请找栈,当世界需要"先来者先走"时请找队列。而你的工作,就是判断当前这个世界要的到底是哪一种。

相关推荐
JAVA面经实录9174 小时前
Java集合大全终极手册(一)
java·开发语言
IT策士4 小时前
Django 从 0 到 1 打造完整电商平台:为什么用 Django 做电商?
后端·python·django
Cosolar4 小时前
吃透 Spring Cloud Gateway:基于 Spring Boot 3 的核心原理、企业级实战与避坑指南
java·spring cloud·架构
千里马-horse4 小时前
gRPC -- Java 基础教程
java·开发语言·grpc
甲方大人请饶命4 小时前
Java-面向对象进阶(qqbb知识点)
java·开发语言
ChoSeitaku4 小时前
07_static_JavaBean_继承_super/this
java·开发语言
江南十四行4 小时前
并发编程(一)
java·jvm·算法
Dicky-_-zhang4 小时前
自动化运维实战:监控告警与自动化运维的完整方案
java·jvm
JavaGuide4 小时前
Claude Code 新功能Agent View 发布:终于不用在一堆终端窗口里找 Agent 了!
前端·后端·agent