linkedlist适合频繁插入删除是因为其双向链表结构支持o(1)的头尾及迭代器定位操作,但随机访问get(i)会退化为o(n),且内存开销大、非线程安全。

为什么 LinkedList 适合频繁插入删除,但别盲目用
因为 LinkedList 在 Java 中是真正的双向链表实现,节点自带 prev 和 next 引用,所以在已知位置(比如通过迭代器或已有节点)做插入/删除时,时间复杂度是 O(1)。但前提是——你得避免触发从头遍历。
常见误用:用 get(int index) 随机访问再删,这会让性能掉到 O(n),比 ArrayList 还慢。它不是“所有操作都快”,只是“在链表语义下、按引用操作时快”。
- 适合场景:
addFirst()、addLast()、removeFirst()、removeLast()、用ListIterator在遍历时add()或remove() - 不适合场景:按索引反复
get(i)后修改;大量随机读 + 少量删;内存敏感环境(每个元素多两个引用字段) - 注意:
LinkedList不是线程安全的,多线程下要加锁或换CopyOnWriteArrayList(但后者写操作更重)
用 ListIterator 替代 for 循环做边遍历边删除
这是最常被忽略的高效用法。普通 for 循环里调 remove(i) 会引发数组搬移(ArrayList)或从头找第 i 个节点(LinkedList),而 ListIterator 持有当前节点引用,remove() 直接断链。
LinkedList<string> list = new LinkedList(Arrays.asList("a", "b", "c", "d"));
ListIterator<string> iter = list.listIterator();
while (iter.hasNext()) {
String s = iter.next();
if ("c".equals(s)) {
iter.remove(); // O(1),不是 list.remove(s)
}
}</string></string>
-
iter.remove()只能紧跟在next()或previous()后调用,否则抛IllegalStateException - 不要混用
list.remove(x)和iter.remove(),后者不检查值相等,只删上一次遍历定位的节点 - 如果需要从后往前删,用
list.listIterator(list.size())初始化迭代器
addFirst / addLast 比 add(index, e) 快得多
addFirst(e) 和 addLast(e) 是 LinkedList 的原生优势操作,直接改头/尾指针,零遍历。而 add(0, e) 或 add(size(), e) 虽然逻辑等价,但底层仍走统一的 add(int, E) 方法,会先调 node(int) 查位置——哪怕查的是头或尾,也要做 if (index 判断,再决定从头还是从尾遍历。
- 明确要插头尾时,无条件用
addFirst()/addLast(),别图省事写add(0, x) -
push(e)等价于addFirst(e),pop()等价于removeFirst(),适合当栈用 -
offerFirst(e)和offerLast(e)是 Deque 接口方法,和上面一样快,且失败时返回false(而非抛异常),适合容错场景
注意内存开销和 GC 压力
每个 Node 对象包含三个字段:E item、Node<e> next</e>、Node<e> prev</e>。小对象(如 Integer)在链表里可能占用内存是值本身的 3–4 倍。高频增删意味着大量短命 Node 对象,容易触发 Young GC。
- 如果元素本身很大(比如大 byte[]),那额外两个引用开销可忽略;但如果存的是
int包装类、String短字符串,就值得权衡 - 替代方案:用
ArrayDeque实现栈/队列(基于循环数组,无节点对象,缓存友好),或自己写轻量级单向链表(如果只需单向操作) - JDK 9+ 的
LinkedList已禁止序列化其内部Node,反序列化时重建,说明官方也意识到它的内存结构不适合持久化
真正关键的不是“用不用 LinkedList”,而是“你是否真的在用它的链表能力”。一旦开始 get(i)、indexOf()、或者把它当普通列表塞进 Stream 并调 filter 再收集,那些 O(1) 操作的优势就全没了。










