LinkedList

LinkedList 源码分析

双向链表,同时实现 List 和 Deque:两端增删 O(1),按下标访问 O(n),非线程安全。
整个类的核心只有三样东西:first/last 两根裸指针、私有的 Node 节点、一组 link*/unlink* 原语——所有公开 API(add/get/offer/poll/push/pop)都是这几个原语的薄包装。

// 基于 JDK 25 (本地 JAVA_Source 仓), java.util.LinkedList —— 骨架与链接/断链原语
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    /**
     * Pointer to first node.
     */
    transient Node<E> first;    // JDK 6 及以前用环形哨兵节点,JDK 7 起改成 first/last 裸指针:空表零分配,代价是每个操作都要判 null

    /**
     * Pointer to last node.
     */
    transient Node<E> last;     // 不变式:size==0 时两者皆 null;否则 first.prev==null 且 last.next==null

    // ... 构造器略:LinkedList() 是空方法体,LinkedList(Collection) 就是 this() + addAll(c),没有任何"预分配容量"的概念

    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;
        }
    }

    /**
     * Links e as first element.
     */
    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;         // 原本是空表:新节点同时是尾
        else
            f.prev = newNode;
        size++;
        modCount++;                 // 结构性修改计数,迭代器 fail-fast 靠它
    }

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;        // succ 是头,插到头前
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

    /**
     * Unlinks non-null first node f.
     */
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC   // 摘下的节点可能还被迭代器攥着,不断链会拖住元素不放
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

    // ... unlinkLast 与 unlinkFirst 完全对称,略

    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;           // 删的是头
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;            // 删的是尾
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
}
// 基于 JDK 25 (本地 JAVA_Source 仓), java.util.LinkedList —— 下标访问路径(慢的根源)
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;        // 每次 get 都从头/尾重新走链表,没有任何位置缓存
}

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);          // 尾插短路,不走 node();addAll 也是同样的特判
    else
        linkBefore(element, node(index));   // 贵在定位,接指针本身 O(1)
}

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

/**
 * Returns the (non-null) Node at the specified element index.
 */
Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {      // 折半:前半段从头走,后半段从尾走,最坏 size/2 步。常数砍半,量级仍是 O(n)
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
// 基于 JDK 25 (本地 JAVA_Source 仓), java.util.LinkedList —— Deque 门面与 fail-fast 迭代器
// 当队列用(FIFO):offer 尾进,poll 头出
public boolean offer(E e) {
    return add(e);
}

public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);     // 空表返回 null 不抛异常。坑:LinkedList 允许存 null,返回 null 有歧义
}

// 当栈用(LIFO):push/pop 都在头部;removeFirst 空表抛 NoSuchElementException
public void push(E e) {
    addFirst(e);
}

public E pop() {
    return removeFirst();
}

private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;    // 创建时抓拍 modCount,之后每步比对

    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);    // 只在构造时定位一次,之后顺指针走——迭代器整趟遍历 O(n) 的关键
        nextIndex = index;
    }

    // ... hasNext / hasPrevious / nextIndex / previousIndex 略

    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();

        lastReturned = next;
        next = next.next;           // 直接跟指针,不再调 node()
        nextIndex++;
        return lastReturned.item;
    }

    public E previous() {
        checkForComodification();
        if (!hasPrevious())
            throw new NoSuchElementException();

        lastReturned = next = (next == null) ? last : next.prev;    // 游标越过末尾时(next==null)从 last 起步:一根游标双向共用
        nextIndex--;
        return lastReturned.item;
    }

    public void remove() {
        checkForComodification();
        if (lastReturned == null)
            throw new IllegalStateException();

        Node<E> lastNext = lastReturned.next;
        unlink(lastReturned);
        if (next == lastReturned)
            next = lastNext;        // previous() 之后删:游标正指着被删节点,要挪开
        else
            nextIndex--;
        lastReturned = null;
        expectedModCount++;         // 自己删的自己认账:同步计数,所以 Iterator.remove 不触发 fail-fast
    }

    // ... set / add / forEachRemaining 略

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

原理串讲

一次 list.add(e) 的完整链路是 add -> linkLast:拿住旧尾 l,new 一个 Node(l, e, null),把 last 指过去,再视 l 是否为 null 决定改 first 还是 l.next——全程只碰两三个指针,不碰其他节点,所以尾插永远 O(1),头插 addFirst -> linkFirst 完全对称。
真正的分水岭在 get(index):checkElementIndex 之后调 node(index)firstlast 一格一格走过去。
所以 for (int i = 0; i < size; i++) list.get(i) 是 O(n2);而 listIterator 只在构造时调一次 node(index) 定位,之后 next() 只做 next = next.next,整趟遍历 O(n)——“遍历 LinkedList 必须用迭代器/for-each”是机制问题,不是代码风格问题。
按值删除 remove(Object o) 同理:从头线性扫到第一个匹配节点再 unlink(null 和非 null 分成两个循环写,因为 null 没法调 equals)。

为什么 JDK 7 要把 JDK 6 的环形哨兵 header 节点改成 first/last 裸指针?哨兵能消掉所有 null 分支(头尾永远有”假节点”垫着),但空链表也得常驻一个对象,而且哨兵的 item 恒为 null,和”用户存的 null 元素”搅在一起容易出错。
裸指针版本每个 link/unlink 多两个 if,换来空表零分配和一条极简的不变式——源码里那段注释掉的 dataStructureInvariants 写得明白:size0 时两头皆 null,否则 first.prev </mark> null && last.next == null

为什么 unlink 要把 item/prev/next 全部置 null,而不是摘下来就完事?节点摘链后本应整体不可达,但迭代器的 lastReturned、外部代码都可能还持有旧节点引用,不断链就会顺着 prev/next 拖住一整串已删除的元素。
clear() 里的原版注释说得更直白:逐个断链能帮助分代 GC(老年代节点引用新生代对象会增加 minor GC 扫描负担),并且保证即便还存在可达的迭代器,内存也照样能释放。

fail-fast 的账本是 modCount:迭代器创建时抓拍到 expectedModCount,之后任何绕过本迭代器的结构修改(包括同一线程在 for-each 里调 list.remove(o))都会让 checkForComodification 抛 ConcurrentModificationException;而 ListItr.remove() 调完 unlink 后手动 expectedModCount++ 把账对平——这就是”遍历中删除必须用 Iterator.remove”的出处。
注意它只是 best-effort 的报警器,不是并发保护。

设计取舍

  • 每个元素一个 Node:两根指针加对象头,内存开销数倍于 ArrayList,且节点散落堆上、缓存命中率差。“链表插入 O(1)“的前提是已经拿到节点,先定位仍要 O(n)。
  • 当纯队列或栈用,ArrayDeque 几乎总是更快(无节点分配、数组连续);LinkedList 的独特价值是同时需要 List 语义和两端 O(1)。
  • 允许 null 元素,代价是 peek/poll 返回 null 分不清”空了”还是”存了个 null”;ArrayDeque 干脆禁 null 来消除歧义。
  • get/set/add/remove 带下标的版本都要走 node(),中间位置批量操作优先用 ListIterator 原地进行,而不是反复按下标调。
  • 非线程安全,modCount 只报警不保护;并发场景换 ConcurrentLinkedQueue / LinkedBlockingQueue。

延伸阅读