LinkedList 源码深度分析(基于 JDK 8)

LinkedList 源码深度分析(基于 JDK 8)

LinkedList 是 Java 集合框架中基于双向链表 实现的容器类,同时实现了 ListDeque 接口,兼具列表(随机访问元素)和双端队列(首尾操作)的特性。其核心优势是首尾 / 中间插入删除效率高 ,缺点是随机访问效率低。本文从源码角度拆解其设计逻辑、核心方法与特性。

一、类结构与核心成员

1. 类定义

复制代码
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    // ... 核心代码
}
  • 继承 AbstractSequentialList:简化顺序访问集合的实现(需重写 listIteratorsize)。

  • 实现 List:支持列表的增删改查、迭代等标准操作。

  • 实现 Deque:支持双端队列的首尾插入 / 删除 / 查询(如 addFirstpollLast)。

  • 实现 Cloneable:支持浅克隆。

  • 实现 Serializable:支持序列化。

2. 核心成员变量

复制代码
// 链表头节点(前驱为 null)
transient Node<E> first;
// 链表尾节点(后继为 null)
transient Node<E> last;
// 链表元素个数(O(1) 访问)
transient int size = 0;
// 序列化版本号
private static final long serialVersionUID = 876323262645176354L;
  • transient 修饰:firstlastsize 不参与默认序列化(自定义 writeObject/readObject 优化序列化逻辑)。

3. 内部节点类(双向链表核心)

LinkedList 的底层是双向链表,每个元素以 Node 节点形式存储,节点间通过「前驱 / 后继引用」关联:

复制代码
private static class Node<E> {
    E item;       // 节点存储的元素
    Node<E> next; // 后继节点引用
    Node<E> prev; // 前驱节点引用
​
    // 构造方法:初始化前驱、元素、后继
    Node(Node<E> prev, E element, Node<E> next) {
        this.prev = prev;
        this.item = element;
        this.next = next;
    }
}

双向链表结构示意图:

复制代码
first                last
  ↓                    ↓
[null] ← Node1 ↔ Node2 ↔ Node3 → [null]

二、构造方法

LinkedList 提供 2 个构造方法,核心是初始化链表的 firstlastsize

1. 无参构造

创建空链表(firstlast 均为 nullsize=0):

复制代码
public LinkedList() {}

2. 带集合参数的构造

将传入的 Collection 元素批量添加到链表尾部:

复制代码
public LinkedList(Collection<? extends E> c) {
    this(); // 调用无参构造初始化空链表
    addAll(c); // 批量添加元素
}

核心依赖 addAll 方法,其逻辑是:

  1. 检查集合非空,转换为数组;

  2. 遍历数组,依次调用 linkLast(添加到尾部);

  3. 更新 sizemodCount(快速失败标记)。

三、核心操作方法解析

LinkedList 的核心操作围绕「双向链表的节点增删改查」展开,关键是通过 node(int index) 高效查找节点,再通过 linkXXX/unlinkXXX 维护节点引用。

1. 节点查找:node(int index)(核心辅助方法)

双向链表的随机访问(按索引找节点)需遍历,但 LinkedList 做了优化:判断索引靠近头还是尾,选择从头部或尾部遍历,减少遍历次数(时间复杂度 O (n/2)):

复制代码
private Node<E> node(int index) {
    // 断言索引合法(0 ≤ index < size)
    // assert isElementIndex(index);
​
    // 索引在左半部分:从头遍历
    if (index < (size >> 1)) { // size >> 1 等价于 size/2(位运算更快)
        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;
    }
}

2. 添加元素

(1)尾部添加:add(E e) / addLast(E e)

add(E e) 本质是 addLast,直接调用 linkLast 维护尾节点:

复制代码
public boolean add(E e) {
    linkLast(e);
    return true;
}
​
// 核心:将元素添加到链表尾部
void linkLast(E e) {
    final Node<E> l = last; // 保存原尾节点
    final Node<E> newNode = new Node<>(l, e, null); // 新节点前驱为原尾节点,后继为 null
    last = newNode; // 新节点成为新尾节点
    if (l == null) // 原链表为空(l 是 null)
        first = newNode; // 新节点同时作为头节点
    else
        l.next = newNode; // 原尾节点的后继指向新节点
    size++;
    modCount++; // 修改次数+1(快速失败机制)
}
(2)指定索引添加:add(int index, E element)

需先找到索引对应的节点,再插入到该节点前面:

复制代码
public void add(int index, E element) {
    checkPositionIndex(index); // 检查索引合法性(0 ≤ index ≤ size)
​
    if (index == size) // 索引等于 size → 尾部添加
        linkLast(element);
    else // 插入到 index 对应的节点前面
        linkBefore(element, node(index));
}
​
// 核心:将元素插入到 succ 节点前面
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev; // 保存 succ 的前驱节点
    final Node<E> newNode = new Node<>(pred, e, succ); // 新节点前驱=pred,后继=succ
    succ.prev = newNode; // succ 的前驱指向新节点
    if (pred == null) // pred 是 null → succ 是头节点
        first = newNode; // 新节点成为新头节点
    else
        pred.next = newNode; // pred 的后继指向新节点
    size++;
    modCount++;
}
(3)头部添加:addFirst(E e)

调用 linkFirst 维护头节点,逻辑与 linkLast 对称:

复制代码
public void addFirst(E e) {
    linkFirst(e);
}
​
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++;
}

3. 删除元素

(1)删除头节点:remove() / removeFirst()

remove() 本质是 removeFirst,调用 unlinkFirst 维护头节点:

复制代码
public E remove() {
    return removeFirst();
}
​
public E removeFirst() {
    final Node<E> f = first;
    if (f == null) // 链表为空 → 抛异常
        throw new NoSuchElementException();
    return unlinkFirst(f);
}
​
// 核心:删除头节点
private E unlinkFirst(Node<E> f) {
    final E element = f.item; // 保存头节点元素(返回用)
    final Node<E> next = f.next; // 保存头节点的后继
    f.item = null; // 置空元素(帮助 GC,避免内存泄漏)
    f.next = null; // 置空后继(断开引用)
    first = next; // 后继节点成为新头节点
    if (next == null) // 后继为 null → 链表为空
        last = null;
    else
        next.prev = null; // 新头节点的前驱置空
    size--;
    modCount++;
    return element;
}
(2)指定索引删除:remove(int index)

先通过 node(index) 找到节点,再调用 unlink 删除:

复制代码
public E remove(int index) {
    checkElementIndex(index); // 检查索引合法性(0 ≤ index < size)
    return unlink(node(index));
}
​
// 核心:删除指定节点 x(通用删除逻辑)
E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next; // x 的后继
    final Node<E> prev = x.prev; // x 的前驱
​
    // 处理前驱节点
    if (prev == null) {
        first = next; // x 是头节点 → 后继成为新头
    } else {
        prev.next = next; // 前驱的后继指向 x 的后继
        x.prev = null; // 断开 x 与前驱的引用
    }
​
    // 处理后继节点
    if (next == null) {
        last = prev; // x 是尾节点 → 前驱成为新尾
    } else {
        next.prev = prev; // 后继的前驱指向 x 的前驱
        x.next = null; // 断开 x 与后继的引用
    }
​
    x.item = null; // 置空元素(帮助 GC)
    size--;
    modCount++;
    return element;
}
(3)删除指定元素:remove(Object o)

遍历链表找到匹配元素(支持 null),调用 unlink 删除:

复制代码
public boolean remove(Object o) {
    if (o == null) { // 处理 null 元素(equals 不能传 null)
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else { // 处理非 null 元素(用 equals 匹配)
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false; // 未找到元素
}

4. 查找与修改

(1)按索引查找:get(int index)

直接调用 node(index) 获取节点,返回其元素(O (n) 时间复杂度):

复制代码
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}
(2)首尾查找:getFirst() / getLast()

直接返回头 / 尾节点的元素(O (1) 时间复杂度):

复制代码
public E getFirst() {
    final Node<E> f = first;
    if (f == null) throw new NoSuchElementException();
    return f.item;
}
​
public E getLast() {
    final Node<E> l = last;
    if (l == null) throw new NoSuchElementException();
    return l.item;
}
(3)修改元素:set(int index, E element)

找到索引对应的节点,替换其 item 并返回旧值:

复制代码
public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element; // 直接替换元素
    return oldVal;
}

四、Deque 接口实现(双端队列特性)

LinkedList 实现了 Deque 接口,支持双端队列的常用操作,核心是复用 linkFirst/linkLast/unlinkFirst/unlinkLast 方法,部分方法区别在于「失败时抛异常还是返回 null」:

操作类型 头部操作(抛异常) 头部操作(返回 null) 尾部操作(抛异常) 尾部操作(返回 null)
添加 addFirst(E) offerFirst(E) addLast(E) offerLast(E)
删除 removeFirst() pollFirst() removeLast() pollLast()
查询 getFirst() peekFirst() getLast() peekLast()

示例:offerFirstaddFirst 的区别:

复制代码
public boolean offerFirst(E e) {
    addFirst(e);
    return true; // 永远返回 true,不会抛异常
}

addFirst 在链表满时抛异常(但 LinkedList 无容量限制,实际不会抛),offerFirst 遵循队列规范返回 true

五、迭代器实现(ListIterator)

LinkedList 重写了 listIterator() 方法,返回 ListItr 内部类,支持双向迭代迭代中增删改(链表迭代效率远高于数组):

复制代码
public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}
​
private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned; // 最后返回的节点
    private Node<E> next; // 下一个要访问的节点
    private int nextIndex; // 下一个节点的索引
    private int expectedModCount = modCount; // 预期修改次数(快速失败)
​
    // 初始化:定位到 index 对应的节点
    ListItr(int index) {
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }
​
    // 是否有下一个元素
    public boolean hasNext() {
        return nextIndex < size;
    }
​
    // 访问下一个元素
    public E next() {
        checkForComodification(); // 检查是否并发修改
        if (!hasNext()) throw new NoSuchElementException();
        lastReturned = next;
        next = next.next; // 指针后移
        nextIndex++;
        return lastReturned.item;
    }
​
    // 是否有前一个元素(双向迭代核心)
    public boolean hasPrevious() {
        return nextIndex > 0;
    }
​
    // 访问前一个元素
    public E previous() {
        checkForComodification();
        if (!hasPrevious()) throw new NoSuchElementException();
        lastReturned = next = (next == null) ? last : next.prev;
        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; // 调整 next 指针
        else
            nextIndex--;
        lastReturned = null; // 避免重复删除
        expectedModCount++; // 更新预期修改次数
    }
​
    // 检查并发修改(快速失败)
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
​
    // 其他方法(add、set)省略...
}
  • 双向迭代 :通过 previous() 方法访问前一个元素,依赖链表的 prev 引用。

  • 快速失败(fast-fail) :迭代器初始化时记录 expectedModCount,每次操作前检查与 modCount 是否一致,不一致则抛 ConcurrentModificationException(避免并发修改导致迭代异常)。

六、克隆与序列化

1. 克隆(浅拷贝)

LinkedList 的 clone() 是浅拷贝:新链表的节点是新创建的,但节点中的 item 引用与原链表一致(若 item 是可变对象,修改会影响双方):

复制代码
public Object clone() {
    LinkedList<E> clone = superClone(); // 调用 Object.clone() 浅拷贝
    // 重置克隆链表为初始状态
    clone.first = clone.last = null;
    clone.size = 0;
    clone.modCount = 0;
    // 遍历原链表,将元素添加到克隆链表(新节点存储原元素引用)
    for (Node<E> x = first; x != null; x = x.next)
        clone.add(x.item);
    return clone;
}

2. 序列化(自定义优化)

LinkedList 重写了 writeObjectreadObject,避免序列化 first/last 节点引用(仅序列化元素本身),减少序列化体积:

复制代码
// 序列化:遍历链表,写入元素(而非节点引用)
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
    s.defaultWriteObject(); // 写入非 transient 成员(如 modCount)
    s.writeInt(size); // 写入元素个数
    // 按顺序写入每个元素
    for (Node<E> x = first; x != null; x = x.next)
        s.writeObject(x.item);
}
​
// 反序列化:重建链表
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject(); // 读取非 transient 成员
    int size = s.readInt(); // 读取元素个数
    // 依次读取元素,添加到尾部
    for (int i = 0; i < size; i++)
        linkLast((E)s.readObject());
}

七、线程安全性

LinkedList 非线程安全

  • 多线程并发修改(如一个线程迭代,另一个线程添加元素)会触发 ConcurrentModificationException(快速失败机制)。

  • 解决方案:

    1. 手动加锁(synchronizedReentrantLock);

    2. 使用 Collections.synchronizedList(new LinkedList<>()) 包装;

    3. 用并发容器 ConcurrentLinkedDeque(无锁实现,高并发场景更优)。

八、优缺点与适用场景

优点

  1. 插入删除高效:首尾操作 O (1),中间操作(已知节点)O (1)(仅需修改节点引用,无需移动元素);

  2. 无容量限制:链表动态扩容,无需像 ArrayList 那样预留多余空间;

  3. 双向迭代支持:实现 Deque 接口,适合做队列、栈、双端队列。

缺点

  1. 随机访问低效:按索引查找元素需遍历链表,时间复杂度 O (n)(ArrayList 是 O (1));

  2. 额外内存开销 :每个元素需存储 prev/next 引用,占用更多内存。

适用场景

  • 频繁进行首尾插入 / 删除操作(如实现队列、栈);

  • 较少进行随机访问(按索引查询);

  • 元素个数不确定(无需预设容量)。

九、与 ArrayList 的核心区别

特性 LinkedList ArrayList
底层实现 双向链表 动态数组
随机访问(get (index)) O(n) O(1)
首尾插入删除 O(1) O (n)(需移动元素 / 扩容)
中间插入删除 O (n)(查找节点)+ O (1)(修改引用) O (n)(移动元素)
内存开销 存储 prev/next 引用 预留数组空间(可能浪费)
线程安全 非线程安全 非线程安全
适用场景 频繁增删、队列 / 栈 频繁查询、随机访问

总结

LinkedList 是基于双向链表的「多功能容器」,设计核心是通过 Node 节点维护前驱 / 后继引用,兼顾 List 和 Deque 的特性。其源码亮点在于:

  • 双向遍历优化(node 方法);

  • 首尾操作高效(linkFirst/linkLast);

  • 自定义序列化与浅克隆;

  • 支持双向迭代与快速失败机制。

相关推荐
im_AMBER42 分钟前
数据结构 12 图
数据结构·笔记·学习·算法·深度优先
西岭千秋雪_42 分钟前
Kafka服务端日志梳理
java·分布式·zookeeper·kafka
咫尺的梦想00743 分钟前
链表——删除链表的倒数第 N 个结点
数据结构·链表
梁bk1 小时前
Redis底层数据结构 -- ziplist, quicklist, skiplist
数据结构·数据库·redis
chéng ௹1 小时前
前端转编码(encodeURIComponent)以及解码(decodeURIComponent)
开发语言·前端·javascript
v***43171 小时前
SpringBoot中Get请求和POST请求接收参数详解
java·spring boot·spring
bbq粉刷匠1 小时前
java刷题-day1
java·开发语言
讓丄帝愛伱1 小时前
excel导出实例
java·python·excel
温轻舟1 小时前
禁毒路上,任重道远 | HTML页面
开发语言·前端·javascript·css·html·温轻舟