LinkedList原生支持双端队列,提供addFirst()、removeLast()等O(1)方法;应声明为Deque类型,优先使用push/pop/offer/poll等语义化方法,避免add(0,…)和线程不安全操作。

LinkedList 本身就能当双端队列用,别封装一层 Deque
Java 的 LinkedList 实现了 Deque 接口,它不是“模拟”双端队列,而是原生支持——addFirst()、addLast()、removeFirst()、removeLast() 全部可用。直接用这些方法,比先转成 ArrayDeque 或包装成 Deque 更直白,也避免误导自己“这只是个列表”。
常见错误是写成:list.add(0, item) 或 list.remove(0) —— 这俩操作在 LinkedList 里虽然平均是 O(1),但实际走的是链表遍历(哪怕首尾也需跳指针),而 addFirst() 等方法才真正复用头尾节点引用,无额外开销。
- 要从头部入队:用
addFirst(),别用add(0, ...) - 要从尾部出队:用
removeLast(),别用remove(size() - 1) - 空队列调用
removeFirst()会抛NoSuchElementException,需要提前判空或改用pollFirst()
用 LinkedList 做栈时,只用 push/pop/peek 就够了
LinkedList 提供了 push()、pop()、peek() 三个栈语义方法,它们内部就是调用 addFirst()、removeFirst()、getFirst()。语义清晰,代码可读性高,没必要手动映射到 add(0, ...) 或 get(0)。
注意:push() 和 pop() 与 Stack 类行为一致,但 Stack 是遗留类(继承自 Vector,同步开销大,且设计违反 LIFO 抽象),官方文档明确建议用 Deque 实现栈——而 LinkedList 就是最轻量的选项之一。
- 入栈统一用
push(item),出栈统一用pop(),不要混用addLast()+removeLast() -
peek()返回栈顶但不移除;element()行为相同,但命名不符合栈语义,容易混淆 - 空栈调用
pop()或peek()同样抛NoSuchElementException,生产环境应优先用poll()/peek()(返回null)并检查空值
性能和线程安全:别被“链表快”骗了
LinkedList 的增删首尾确实是 O(1),但它的内存开销比 ArrayDeque 大——每个元素额外带两个引用(prev/next),GC 压力略高;随机访问却是 O(n),远慢于数组实现。所以它适合频繁首尾操作、元素不多、不关心内存紧凑性的场景。
另一个关键点:所有 LinkedList 方法都**不保证线程安全**。如果多个线程同时 push() 和 pop(),即使只操作首尾,也会因节点指针修改竞态而破坏结构——出现 ConcurrentModificationException 或静默数据丢失。真要并发,得套 Collections.synchronizedList()(但会锁整个列表),或直接换 ConcurrentLinkedDeque。
- 单线程下,
LinkedList做栈/双端队列完全没问题;高频场景下,ArrayDeque通常更快(缓存友好、无对象头开销) - 别因为“它实现了 Deque”就默认它是线程安全的——接口不承诺同步,实现类也没加锁
- 如果栈深度固定或可预估,
ArrayDeque几乎总是更优;只有需动态频繁插入中间位置时,才考虑LinkedList
实际写法:声明类型别写 LinkedList,写 Deque
用 Deque,而不是 LinkedList。前者强调用途(栈/双端队列),后者暴露实现细节,后续想换成 ArrayDeque 就得改声明、改构造,还可能误用 get(int) 这种非队列操作。
这种声明方式也自然规避了部分陷阱:IDE 会提示你用 push() 而不是 add(0, ...),编译器会阻止调用 indexOf() 或 sort() 这类非双端队列语义的方法。
- 变量/参数类型优先用
Deque、Queue、Stack(仅限遗留兼容),而非具体实现类 - 构造时才指定
new LinkedList(),切换实现只需改这一行 - 一旦声明为
Deque,你就不会再手贱写list.get(5)这种破坏抽象的操作
用 LinkedList 做栈或双端队列本身没毛病,真正容易翻车的是类型声明、异常处理和线程假设——这些地方不写注释、不加防护,运行时才暴露,而且问题往往藏在边界条件里。











