ArrayDeque 比 LinkedList 更适合做栈和队列,因其基于循环数组、无节点开销、缓存友好、操作平均 O(1);LinkedList 因链表特性导致内存分配频繁、遍历慢、GC 压力大。

ArrayDeque 比 LinkedList 更适合做栈和队列
Java 的 Deque 接口本身不提供实现,真正干活的是 ArrayDeque 和 LinkedList。但多数场景下,ArrayDeque 是更优解——它用循环数组实现,没有节点对象开销,缓存友好,所有操作平均 O(1);而 LinkedList 是链表,每次新增节点都要分配内存,遍历慢,GC 压力大。
常见错误现象:new LinkedList() 被当成“通用双端队列”无脑使用,尤其在高频 push/pop 场景(比如解析表达式、BFS 队列),性能会明显劣于 ArrayDeque。
- 栈场景(LIFO):优先用
ArrayDeque的push()/pop(),别用addFirst()/removeFirst() - 队列场景(FIFO):用
offer()/poll(),它们对ArrayDeque和LinkedList行为一致,但前者更快 -
ArrayDeque不允许null元素,插入null会直接抛NullPointerException;LinkedList允许,这点容易踩坑
ArrayDeque 的扩容机制影响初始容量选择
ArrayDeque 底层是动态扩容的循环数组,初始容量默认为 16,每次扩容为当前容量的 2 倍(且始终是 2 的幂)。扩容本身不慢,但频繁扩容会触发数组复制,带来短暂延迟和额外内存占用。
使用场景:如果你能预估队列峰值大小(比如处理固定大小滑动窗口、日志缓冲区),显式指定初始容量能避免多次扩容。
立即学习“Java免费学习笔记(深入)”;
- 构造时传入预期容量:
new ArrayDeque(1024),比默认的 16 更合理 - 容量必须是正整数,传 0 会被自动设为 1;传负数会抛
IllegalArgumentException - 扩容后新数组长度仍是 2 的幂,所以即使你传 1000,内部实际分配的是 1024
ArrayDeque 不是线程安全的,别在并发场景裸用
ArrayDeque 所有方法都不加锁,多线程同时读写必然出错——可能抛 ConcurrentModificationException,也可能静默数据丢失或无限循环。
错误现象:单元测试偶尔失败、生产环境偶发 ArrayIndexOutOfBoundsException 或死循环,排查时发现只在高并发压测下复现。
- 需要线程安全?用
Collections.synchronizedDeque(new ArrayDeque()),但注意它只保证单个操作原子性,复合操作(如先isEmpty()再poll())仍需手动同步 - 高并发队列场景,考虑
ConcurrentLinkedDeque,但它基于链表,性能不如ArrayDeque,且不支持Iterator.remove() - 千万别把
ArrayDeque放进static字段然后多个线程共用——这是最典型的误用
peek() 和 element() 的区别常被忽略
这两个方法都“看”队首/栈顶元素,但异常策略不同:peek() 在队列为空时返回 null;element() 则抛 NoSuchElementException。很多人只记住了“不抛异常就用 peek()”,却忽略了类型约束带来的隐含风险。
使用场景:当你用泛型 ArrayDeque<string></string>,调用 peek() 返回 null 是合法的;但如果用原始类型 ArrayDeque(不推荐),或者后续代码没判空就直接调用 .length(),就会爆 NullPointerException。
- 习惯性用
if (!deque.isEmpty()) { String s = deque.peek(); ... },而不是依赖element()的异常流控制逻辑 -
element()更适合“本该非空但为空即表示严重逻辑错误”的场景,比如从已确认初始化的配置队列取值 - 别在
switch或Optional.ofNullable()里直接传peek()结果——万一返回null,Optional.ofNullable(null)是合法的,但容易掩盖空值传播路径
List 用,也不能在遍历时修改结构——这些限制不是缺陷,而是设计取舍。真要随机访问或边遍历边删,换 ArrayList 或加锁的 CopyOnWriteArrayList 更合适。










