LinkedList源码分析

118 阅读4分钟

LinkedList 源码深度解析(基于双向链表)

前置知识铺垫

LinkedList 的底层核心是 双向链表,每个节点(Node)包含前驱指针(prev)、数据(item)、后继指针(next)。建议先阅读 Java 简单模拟双向链表,理解双向链表的基本操作逻辑。

一、核心结论

LinkedList 维护的是 双向链表结构,通过 first(头节点)、last(尾节点)、size(元素个数)、modCount(修改次数,用于快速失败机制)四个核心属性管理数据。

二、核心组件解析

1. 节点内部类(Node)

双向链表的基础单元,私有静态内部类,封装了节点的核心信息:

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

2. 无参构造器

public LinkedList() {}
  • 实例化后状态

    • size=0(无元素)

    • first=null(头节点为空)

    • last=null(尾节点为空)

    • modCount=0(未修改)

  • 特点:仅初始化链表结构,不分配额外空间,高效轻量。

三、核心方法源码解析

1. 添加元素:add (E e)

默认在链表 尾部添加元素,底层调用 linkLast(E e) 实现。

源码与逐行解析
public boolean add(E e) {
    linkLast(e);  // 核心逻辑:将元素作为尾节点加入
    return true;  // 固定返回true(List接口规范,添加成功必返回true)
}

// 核心辅助方法:将e作为最后一个元素链接到链表
void linkLast(E e) {
    final Node<E> l = last;  // 保存当前尾节点(记为l)
    // 新建节点:前驱为l(原尾节点),数据为e,后继为null(新尾节点无后继)
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;  // 更新尾节点为新节点

    if (l == null) {  // 若原尾节点为null → 链表为空,新节点既是头也是尾
        first = newNode;
    } else {  // 链表非空 → 原尾节点的后继指向新节点
        l.next = newNode;
    }

    size++;     // 元素个数+1
    modCount++; // 修改次数+1(迭代器快速失败的关键)
}
关键逻辑链路
  • 空链表添加(l 为 null):
  1. ①newNode.prev = null 且 ①newNode.next = null

  2. ③头节点和②尾节点都 = newNode;

  • 非空链表添加:
  1. ①newNode.prev = 原尾节点 且 ③原尾节点.next = newNode(与链表尾建立连接)

  2. ②尾节点 = newNode。

①②③为代码中顺序

2. 删除元素:remove ()(无参)

默认删除 头节点,底层调用 removeFirst() → unlinkFirst(Node<E> f) 实现。

源码与逐行解析
public E remove() {
    return removeFirst();  // 无参删除默认删除头节点
}

// 删除头节点
public E removeFirst() {
    final Node<E> f = first;  // 保存当前头节点(记为f)
    if (f == null) {          // 头节点为空 → 链表无元素,抛异常
        throw new NoSuchElementException();
    }
    return unlinkFirst(f);    // 核心逻辑:解除头节点的链接
}

// 核心辅助方法:解除非空头节点f的链接(内部调用,确保f≠null)
private E unlinkFirst(Node<E> f) {
    // 断言:确保f是头节点且非空(避免外部调用出错)
    // assert f == first && f != null;

    final E element = f.item;  // 保存头节点的数据(用于返回)
    final Node<E> next = f.next;  // 保存头节点的后继节点(记为next)

    // 断开f的引用,帮助GC回收(避免内存泄漏)
    f.item = null;
    f.next = null;

    first = next;  // 更新头节点为原头节点的后继(next)

    if (next == null) {  // next为null → 原链表只有一个节点,删除后链表为空
        last = null;
    } else {  // next非空 → 新头节点的前驱设为null(断开与原头节点的关联)
        next.prev = null;
    }

    size--;     // 元素个数-1
    modCount++; // 修改次数+1
    return element;  // 返回被删除的元素数据
}
关键逻辑链路
  • 链表仅 1 个元素 → 删除后 first=last=null;

  • 链表多个元素 → 新头节点 = 原头节点.next → 新头节点.prev = null → 原头节点被 GC 回收。

注意事项
  • 若链表为空(first=null),调用 remove() 会抛出 NoSuchElementException,需提前判断 size>0
  • f.item = null 和 f.next = null 是关键:手动断开原头节点的引用,让 GC 识别为垃圾并回收。

四、核心总结

核心点细节说明
数据结构双向链表,节点间通过 prev/next 双向关联
构造器无参构造器仅初始化空链表,无额外空间开销
add(E e)尾插法,时间复杂度 O (1)(直接操作 last 节点)
remove()头删法,时间复杂度 O (1)(直接操作 first 节点)
modCount记录结构修改次数,迭代时修改会抛 ConcurrentModificationException
GC 优化删除节点时手动断开引用(item/next 设为 null)