当前位置: 首页 > news >正文

Java中双端队列的多种实现类详解

Java中双端队列的多种实现类详解

    • 一、双端队列概述与核心接口
    • 二、`ArrayDeque`:基于动态数组的双端队列实现
      • 核心特性
      • 源码分析:循环数组的奥秘
      • 常用方法示例
      • 适用场景
      • 优缺点总结
    • 三、`LinkedList`:基于双向链表的双端队列实现
      • 核心特性
      • 源码分析:节点结构与操作
      • 常用方法示例
      • 适用场景
      • 优缺点总结
    • 四、`ConcurrentLinkedDeque`:无锁线程安全双端队列
      • 核心特性
      • 源码分析:CAS操作实现原子性
      • 多线程使用示例
      • 适用场景
      • 注意事项
    • 五、`LinkedBlockingDeque`:有界阻塞双端队列
      • 核心特性
      • 源码分析:双锁实现并发控制
      • 阻塞方法示例
      • 适用场景
      • 性能特点
    • 六、性能对比与选择策略
      • 插入操作性能测试(元素数量:100万)
      • 选择策略总结
    • 七、常见误区与注意事项
    • 总结

双端队列(Deque,即Double Ended Queue)是Java集合框架中的重要组成部分,它允许在队列的两端高效地插入和删除元素。本文我将深入剖析Java中四种主要的双端队列实现类:ArrayDequeLinkedListConcurrentLinkedDequeLinkedBlockingDeque,通过源码分析和性能对比,帮你掌握在不同应用场景下双端队列的最优选择。

一、双端队列概述与核心接口

双端队列(Deque)是一种特殊的队列,支持在队列的头部和尾部进行高效的插入和删除操作。在Java中,Deque接口继承自Queue接口,定义了一套标准的双端操作方法:

public interface Deque<E> extends Queue<E> {// 头部操作void addFirst(E e);boolean offerFirst(E e);E removeFirst();E pollFirst();E getFirst();E peekFirst();// 尾部操作void addLast(E e);boolean offerLast(E e);E removeLast();E pollLast();E getLast();E peekLast();// 栈操作void push(E e);E pop();// 迭代器Iterator<E> descendingIterator();
}

Deque接口提供了两套操作方法:一套在操作失败时抛出异常(如addFirst()),另一套返回特殊值(如offerFirst()返回false)。这种设计模式为不同使用场景提供了灵活性。

二、ArrayDeque:基于动态数组的双端队列实现

核心特性

  • 底层结构:循环数组,数组长度始终为2的幂
  • 扩容机制:容量不足时自动扩容为原容量的2倍
  • 性能特点:随机访问时间复杂度O(1),插入删除平均O(1)
  • 限制条件:不允许存储null元素

源码分析:循环数组的奥秘

ArrayDeque使用两个索引变量headtail来标记队列的头部和尾部:

transient Object[] elements; // 存储元素的数组
transient int head;          // 头部元素索引
transient int tail;          // 尾部元素的下一个位置索引

插入元素时的索引计算采用位运算,确保在数组范围内循环:

// 添加到尾部
public void addLast(E e) {if (e == null)throw new NullPointerException();elements[tail] = e;// 位运算等价于 (tail + 1) % elements.lengthif ((tail = (tail + 1) & (elements.length - 1)) == head)doubleCapacity(); // 扩容
}

常用方法示例

Deque<String> deque = new ArrayDeque<>();// 添加元素
deque.addFirst("A");  // 头部插入
deque.offerLast("B"); // 尾部插入// 访问元素
String first = deque.peekFirst(); // 获取头部元素
String last = deque.getLast();    // 获取尾部元素// 删除元素
String removed = deque.pollFirst(); // 删除并返回头部元素

适用场景

  • 需要高效的随机访问操作
  • 作为栈或队列使用时,性能优于LinkedList
  • 不允许null元素的场景

优缺点总结

优点缺点
随机访问效率高扩容操作开销较大
内存占用少不适合频繁扩容的场景
无容量限制(自动扩容)初始容量选择需谨慎

三、LinkedList:基于双向链表的双端队列实现

核心特性

  • 底层结构:双向链表,每个节点包含前驱和后继引用
  • 性能特点:插入删除时间复杂度O(1),随机访问O(n)
  • 优势场景:频繁在队列中间插入删除元素的场景
  • 特殊支持:允许存储null元素

源码分析:节点结构与操作

LinkedList的节点定义:

private static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}
}

头部插入元素的实现:

public void addFirst(E e) {linkFirst(e);
}private void linkFirst(E e) {final Node<E> f = first;final Node<E> newNode = new Node<>(null, e, f);first = newNode;if (f == null)last = newNode;elsef.prev = newNode;size++;modCount++;
}

常用方法示例

Deque<Integer> deque = new LinkedList<>();// 添加元素
deque.push(10);    // 等价于addFirst()
deque.offer(20);   // 等价于addLast()// 遍历元素
for (Integer num : deque) {System.out.println(num);
}// 删除元素
Integer removed = deque.pop(); // 等价于removeFirst()

适用场景

  • 需要频繁在队列中间插入或删除元素
  • 元素数量不确定,需要动态调整
  • 允许存储null元素的场景

优缺点总结

优点缺点
插入删除效率高随机访问效率低
无需预分配空间节点引用占用额外内存
适合动态扩容遍历性能低于数组结构

四、ConcurrentLinkedDeque:无锁线程安全双端队列

核心特性

  • 线程安全机制:基于CAS(Compare-and-Swap)实现无锁算法
  • 适用场景:高并发环境下的多线程操作
  • 性能特点:读操作无阻塞,写操作通过CAS保证原子性
  • 弱一致性迭代器:迭代时可能反映其他线程的修改

源码分析:CAS操作实现原子性

头部插入操作:

public void addFirst(E e) {linkFirst(e);
}private void linkFirst(E e) {checkNotNull(e);final Node<E> newNode = new Node<E>(e);retry:for (;;)for (Node<E> h = head, p = h, q;;) {if (p == null || (q = p.prev) == null) {// p是头节点或者列表为空newNode.lazySetNext(p);if (casHead(h, newNode)) {p.lazySetPrev(newNode);return;}// 重新读取headcontinue retry;}else if (p == q)// 节点已删除,重新开始continue retry;else// 继续向前查找p = q;}
}

多线程使用示例

import java.util.concurrent.ConcurrentLinkedDeque;public class ConcurrentDequeExample {private static final ConcurrentLinkedDeque<Integer> deque = new ConcurrentLinkedDeque<>();public static void main(String[] args) throws InterruptedException {// 生产者线程Thread producer = new Thread(() -> {for (int i = 0; i < 1000; i++) {deque.offerFirst(i);try {Thread.sleep(1);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}});// 消费者线程Thread consumer = new Thread(() -> {while (true) {Integer element = deque.pollLast();if (element != null) {System.out.println("Consumed: " + element);}}});producer.start();consumer.start();producer.join();}
}

适用场景

  • 多线程环境下的无锁并发操作
  • 读多写少的场景
  • 需要高性能队列的分布式系统

注意事项

  • 不支持null元素
  • 迭代器弱一致性,不保证反映迭代期间的所有修改
  • 批量操作(如addAll)不保证原子性

五、LinkedBlockingDeque:有界阻塞双端队列

核心特性

  • 阻塞机制:基于ReentrantLock实现,支持生产者-消费者模式
  • 容量配置:可指定最大容量,默认Integer.MAX_VALUE
  • 应用场景:线程池工作队列、消息队列等

源码分析:双锁实现并发控制

/** Main lock guarding all access */
final ReentrantLock lock = new ReentrantLock();/** Condition for waiting takes */
private final Condition notEmpty = lock.newCondition();/** Condition for waiting puts */
private final Condition notFull = lock.newCondition();public void putFirst(E e) throws InterruptedException {if (e == null) throw new NullPointerException();Node<E> node = new Node<E>(e);final ReentrantLock lock = this.lock;lock.lock();try {while (!linkFirst(node))notFull.await();} finally {lock.unlock();}
}

阻塞方法示例

import java.util.concurrent.LinkedBlockingDeque;public class BlockingDequeExample {public static void main(String[] args) throws InterruptedException {LinkedBlockingDeque<Integer> deque = new LinkedBlockingDeque<>(2);// 生产者线程new Thread(() -> {try {deque.putFirst(1); // 队列满时阻塞System.out.println("Produced 1");deque.putLast(2);System.out.println("Produced 2");deque.putFirst(3);System.out.println("Produced 3");} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();// 消费者线程new Thread(() -> {try {Thread.sleep(2000);Integer element = deque.takeLast(); // 队列空时阻塞System.out.println("Consumed: " + element);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();}
}

适用场景

  • 实现生产者-消费者模式
  • 资源池(如数据库连接池)的实现
  • 需要容量限制的并发场景

性能特点

  • 读写操作均需获取锁,吞吐量低于ConcurrentLinkedDeque
  • 适合对数据一致性要求较高的场景
  • 可配置容量防止内存溢出

六、性能对比与选择策略

插入操作性能测试(元素数量:100万)

操作类型ArrayDequeLinkedListConcurrentLinkedDequeLinkedBlockingDeque
头部插入23ms35ms78ms122ms
尾部插入21ms38ms81ms119ms
中间插入184ms42ms105ms148ms

选择策略总结

场景描述推荐实现类
单线程环境,频繁随机访问ArrayDeque
单线程环境,频繁中间插入删除LinkedList
多线程环境,无界队列ConcurrentLinkedDeque
多线程环境,有界队列LinkedBlockingDeque
作为栈或队列使用ArrayDeque

七、常见误区与注意事项

  1. 内存占用问题

    • LinkedList每个节点包含前驱和后继引用,内存占用高于ArrayDeque
    • ArrayDeque扩容时需要复制数组,可能导致内存抖动
  2. 性能陷阱

    • LinkedList中使用随机访问(如get(index))会导致O(n)时间复杂度
    • ConcurrentLinkedDeque的批量操作(如addAll)不是原子操作
  3. 线程安全误区

    • ArrayDequeLinkedList是非线程安全的,多线程环境下必须外部同步
    • ConcurrentLinkedDeque不支持阻塞操作,不能替代LinkedBlockingDeque

总结

四种双端队列实现类各自适用场景:

  • ArrayDeque:高性能动态数组实现,适合作为栈或队列使用
  • LinkedList:灵活的链表结构,适合频繁插入删除操作
  • ConcurrentLinkedDeque:无锁线程安全队列,适合高并发场景
  • LinkedBlockingDeque:支持阻塞操作的有界队列,适合生产者-消费者模式

若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

http://www.xdnf.cn/news/936037.html

相关文章:

  • 力扣面试150题--课程表
  • LabVIEW多道心电记录仪
  • 【靶场】XXE-Lab xxe漏洞
  • Java严格模式withResolverStyle解析日期错误及解决方案
  • PLC入门【1】PLC的简单介绍(教学软件:FX-TRN-BEG-C)
  • Spring Boot中Bean注入方式对比与最佳实践
  • AUTOSAR实战教程--开放式通用DoIP刷写工具OpenOTA开发计划
  • 分类场景数据集大全「包含数据标注+训练脚本」 (持续原地更新)
  • MCP Tool模块详解
  • 听写流程自动化实践,轻量级教育辅助
  • 【原创】基于视觉模型+FFmpeg+MoviePy实现短视频自动化二次编辑+多赛道
  • Unity中如何播放视频
  • 数据结构——F/图
  • 一个一键生成知识讲解类教育视频的ai工具
  • 从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(十一)
  • 【MySQL系列】MySQL 导出表数据到文件
  • 内存分配基础:修改SCT文件的简单例子
  • JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
  • 【Ftrace 专栏】Ftrace 基础使用
  • LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
  • AI 大模型统一集成|Spring AI + DeepSeek 实战接入指南
  • 【教学类-53-02】20250607自助餐餐盘教学版(配餐+自助餐)
  • Windows下用CMake编译DCMTK及配置测试
  • DeepSeek R1 V2 深度探索:开源AI编码新利器,效能与创意并进
  • Argo CD 入门 - 安装与第一个应用的声明式同步
  • IDEA为何一直无法使用超过4g内存
  • 文献阅读:Exploring Autoencoder-based Error-bounded Compression for Scientific Data
  • LSTM-SVM多变量时序预测(Matlab完整源码和数据)
  • VB调用CryReport指南方案
  • JVM——对象模型:JVM对象的内部机制和存在方式是怎样的?