linkedlist天然实现deque接口,可直接用作双端队列或栈;应优先用deque接口声明变量,使用push/pop/peek或addfirst/removefirst等语义明确的方法,避免混用add/remove及随机访问操作。

LinkedList 本身就能当双端队列用,别自己封装
Java 的 LinkedList 实现了 Deque 接口,它天然支持头尾插入/删除,无需额外包装就能直接当双端队列(Deque)使用。很多人误以为要继承或包装 LinkedList 才能模拟栈或队列,其实只是没看清它的方法命名规则。
关键点在于:优先用 Deque 接口声明变量,而不是 LinkedList —— 这样既语义清晰,又避免误调用非队列语义的方法(比如 get(int) 随机访问,会触发 O(n) 遍历)。
-
addFirst(e)/addLast(e)对应双端入队 -
removeFirst()/removeLast()对应双端出队(空时抛NoSuchElementException) -
pollFirst()/pollLast()更安全,空时返回null - 当栈用:统一用
push(e)、pop()、peek()—— 它们底层就是addFirst/removeFirst
用作栈时,别混用 push/pop 和 add/remove
push() 和 pop() 是 Deque 接口为栈语义提供的专用方法,它们只操作头部(first),行为确定且可读性强。但如果你不小心用了 add(e)(等价于 addLast)再配 pop(),就会出错——一个插尾,一个删头,数据顺序错乱。
常见错误现象:Stack 行为不一致、元素“消失”、抛 NoSuchElementException 却查不出原因。
- 栈场景下,**只用
push()/pop()/peek()**,完全避开add/remove系列方法 - 如果必须用
LinkedList实例变量(比如需要调用size()或遍历),仍建议通过Deque引用调用栈方法,保持意图明确 -
Stack类已过时,ArrayDeque性能更好,但LinkedList在需要频繁中间插入/删除时仍有存在价值
性能和线程安全:LinkedList 不是万能的
LinkedList 的双端操作确实是 O(1),但它在随机访问(get(int))、迭代(尤其是 for-each)时比 ArrayList 慢得多,因为每个节点都要跳指针。更关键的是:它**不是线程安全的**,多线程下直接用 push/pop 会丢数据或抛异常。
- 单线程栈/双端队列:放心用,但注意别触发
get(i)或indexOf() - 多线程场景:不要加
synchronized块去包push/pop—— 容易死锁或漏锁;改用ConcurrentLinkedDeque(无锁、弱一致性)或ArrayBlockingQueue(阻塞、强一致性) - 内存开销:每个元素多存两个引用(prev/next),大数据量时比
ArrayList或ArrayDeque更占内存
构造和初始化时的隐含陷阱
LinkedList 没有带初始容量的构造函数,所以不能像 ArrayList 那样预分配空间。但更隐蔽的问题是:用 Arrays.asList(...) 初始化后,再调用 push() 可能抛 UnsupportedOperationException —— 因为 Arrays.asList 返回的是不可变的 List 子类,而你可能误把它赋给了 Deque 变量。
- 正确初始化双端队列:
Deque<integer> dq = new LinkedList(Arrays.asList(1, 2, 3));</integer> - 错误写法:
Deque<integer> dq = Arrays.asList(1, 2, 3);</integer>→ 编译都过不去,因为类型不匹配 - 如果从集合构建,确保传入的是可修改的
Collection,比如new ArrayList(...)再转LinkedList
真正容易被忽略的是:LinkedList 的 remove(Object) 是从头遍历找值,O(n);而双端队列语义里本不该做这种操作。一旦在栈或队列逻辑里写了 remove("someValue"),就说明设计意图已经混乱了。










