【Java】从源码深入理解LinkedList

从源码深入理解LinkedList

LinkedList的整体架构

LinkedList的继承与实现关系如下:

java 复制代码
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

从继承结构可以看出:

  • AbstractSequentialList:提供顺序访问的骨架实现
  • List接口:提供列表的基本操作
  • Deque接口:支持双端队列操作(这是LinkedList的特色)
  • Cloneable:支持克隆
  • Serializable:支持序列化

LinkedList的核心数据结构

LinkedList的底层是一个双向链表,其节点定义如下:

java 复制代码
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;
    }
}

每个节点都持有对前驱和后继节点的引用。

LinkedList的核心成员变量

java 复制代码
// 链表长度(节点个数)
transient int size = 0;

// 指向第一个节点
transient Node<E> first;

// 指向最后一个节点
transient Node<E> last;
  • 这三个成员变量都用transient修饰,意味着它们不会参与默认序列化
  • LinkedList重写了writeObjectreadObject方法来手动实现序列化,只序列化实际存储的元素,而不序列化链表结构

LinkedList的构造器

无参构造

java 复制代码
// 构造一个空链表
public LinkedList() {
}

传入一个已有的集合

java 复制代码
// 通过已有集合构造
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

创建 LinkedList 对象

java 复制代码
// 方式1:使用父接口引用
List<Integer> LinkedList1 = new LinkedList<>();

// 方式2:使用具体实现类引用
LinkedList<Integer> LinkedList2 = new LinkedList<>();
特性 List LinkedList1 LinkedList LinkedList2
变量类型 List接口 LinkedList类
可调用的方法 只有List接口定义的方法 LinkedList类的所有公开方法

使用 List 接口引用(方式1)

java 复制代码
List<Integer> LinkedList1 = new LinkedList<>();
LinkedList1.add(1);        // ✅ List接口方法
LinkedList1.add(2);        
LinkedList1.get(0);        // ✅ List接口方法

// ❌ 无法调用LinkedList特有方法
LinkedList1.addFirst(0);   // 编译错误!List接口没有addFirst
LinkedList1.addLast(3);    // 编译错误!
  • 只需要使用 List 的标准操作(add, remove, get, set 等)
  • 希望保持代码灵活性(未来可以切换为 ArrayList)

使用 LinkedList 类引用(方式2)

java 复制代码
LinkedList<Integer> LinkedList2 = new LinkedList<>();
LinkedList2.add(1);         // ✅ 继承自List
LinkedList2.addFirst(0);    // ✅ LinkedList特有方法
LinkedList2.addLast(3);     // ✅ LinkedList特有方法
LinkedList2.peek();         // ✅ 队列方法
LinkedList2.poll();         // ✅ 队列方法
  • 需要使用 LinkedList 特有方法(addFirst, addLast, peek, poll 等)

  • 明确利用双端队列特性

    java 复制代码
    // 作为双端队列使用
    LinkedList<Integer> queue = new LinkedList<>();
    queue.addFirst(1);
    queue.addLast(2);

LinkedList的常用方法:源码解读

添加方法

头插法:addFirst(E e)

java 复制代码
public void addFirst(E e) {
    linkFirst(e);
}

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

尾插法:add(E e) / addLast(E e)

java 复制代码
public boolean add(E e) {
    linkLast(e);
    return true;
}

public void addLast(E e) {
    linkLast(e);
}

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

指定位置插入:add(int index, E element)

java 复制代码
public void add(int index, E element) {
    checkPositionIndex(index);  // 检查索引合法性
    
    if (index == size)
        linkLast(element);       // 尾部插入
    else
        linkBefore(element, node(index));  // 中间插入
}

// 获取指定位置的节点
Node<E> node(int index) {
    // 关键优化:判断index靠近哪一端
    if (index < (size >> 1)) {  // 靠近头部,从头开始找
        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;
    }
}

// 在指定节点之前插入
void linkBefore(E e, Node<E> succ) {
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

关键点node(int index)方法利用双向链表的特性,通过比较index与size/2的大小来决定是从头遍历还是从尾遍历,这是LinkedList查询操作的核心优化。

添加集合:boolean addAll(Collection<? extends E> c) -

java 复制代码
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);  // 从尾部开始添加
}

public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);
    
    Object[] a = c.toArray();     // 将集合转为数组
    int numNew = a.length;
    if (numNew == 0)
        return false;
    
    // 找到插入位置的前驱和后继节点
    Node<E> pred, succ;
    if (index == size) {           // 尾插
        succ = null;
        pred = last;
    } else {                       // 中间插
        succ = node(index);        // 找到index位置的节点
        pred = succ.prev;
    }
    
    // 批量添加节点
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }
    
    // 连接后继部分
    if (succ == null) {
        last = pred;               // 尾插,更新last
    } else {
        pred.next = succ;
        succ.prev = pred;
    }
    
    size += numNew;
    modCount++;
    return true;
}

删除方法

删除指定位置:E remove(int index)

java 复制代码
public E remove(int index) {
    checkElementIndex(index);        // 检查 0 ≤ index < size
    return unlink(node(index));      // 找到节点并删除
}

// 核心删除逻辑
E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;     // 后继节点
    final Node<E> prev = x.prev;     // 前驱节点
    
    // 处理前驱指针
    if (prev == null) {              // x是头节点
        first = next;                // 头节点指向next
    } else {
        prev.next = next;            // 前驱的next指向后继
        x.prev = null;               // 断开引用,帮助GC
    }
    
    // 处理后继指针
    if (next == null) {              // x是尾节点
        last = prev;                 // 尾节点指向prev
    } else {
        next.prev = prev;            // 后继的prev指向前驱
        x.next = null;               // 断开引用,帮助GC
    }
    
    x.item = null;                   // 清空元素,帮助GC
    size--;
    modCount++;
    return element;
}

删除第一次出现的对象:boolean remove(Object o)

java 复制代码
public boolean remove(Object o) {
    if (o == null) {                    // 删除null
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {                            // 删除非null对象
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

特点:需要遍历链表找到第一个匹配的元素,时间复杂度 O(n)。

查找方法

获取元素:E get(int index)

java 复制代码
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;    // 先定位节点,再取值
}

返回第一个匹配的下标: int indexOf(Object o)

java 复制代码
public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

返回最后一个匹配的下标:int lastIndexOf(Object o)

java 复制代码
public int lastIndexOf(Object o) {
    int index = size;
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {  // 从尾向前遍历
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        for (Node<E> x = last; x != null; x = x.prev) {  // 从尾向前遍历
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    return -1;
}

注意lastIndexOf 是从尾部向前遍历,这样找到的第一个就是最后一个匹配项。

修改与清理方法

修改元素: E set(int index, E element)

java 复制代码
public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);      // 先定位节点
    E oldVal = x.item;
    x.item = element;             // 直接替换item值
    return oldVal;
}

清空链表: void clear()

java 复制代码
public void clear() {
    // 逐个断开所有节点引用,帮助GC回收
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;   // 头尾置空
    size = 0;
    modCount++;
}

是否包含: boolean contains(Object o)

java 复制代码
public boolean contains(Object o) {
    return indexOf(o) != -1;   // 复用indexOf方法
}

截取方法:List subList(int fromIndex, int toIndex)

java 复制代码
// LinkedList本身没有实现subList,继承自AbstractList
public List<E> subList(int fromIndex, int toIndex) {
    return (this instanceof RandomAccess ?
            new RandomAccessSubList<>(this, fromIndex, toIndex) :
            new SubList<>(this, fromIndex, toIndex));
}

重要提醒subList 返回的是原列表的视图,对子列表的修改会影响原列表!

java 复制代码
LinkedList<String> list = new LinkedList<>();
list.addAll(Arrays.asList("A", "B", "C", "D"));
List<String> sub = list.subList(1, 3);  // [B, C]

sub.set(0, "X");        // 修改子列表
System.out.println(list); // [A, X, C, D] 原列表也被修改!

队列操作: offer(E e) / poll() / peek()

java 复制代码
// 入队(尾部)
public boolean offer(E e) {
    return add(e);
}

// 出队(头部)
public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

// 查看队首元素
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

LinkedList vs ArrayList

时间复杂度对比

操作 ArrayList LinkedList
get(index) O(1) O(n)
add(尾部) O(1) 均摊 O(1)
add(index) O(n) O(n) *
remove(index) O(n) O(n) *
remove(头部) O(n) O(1)
addFirst O(n) O(1)

*注:LinkedList的中间插入/删除虽然定位到index需要O(n)时间,但插入/删除操作本身是O(1);ArrayList则是移位操作O(n)。

我来从源码角度深入解读 LinkedList 的各种遍历方法,以及它们的底层实现原理和适用场景。

LinkedList的遍历

1)普通 for 循环

java 复制代码
// 这种方式性能最差!
for (int i = 0; i < linkedList2.size(); i++) {
    System.out.print(linkedList2.get(i) + " ");
}

源码分析

java 复制代码
// 每次调用 get(i) 都会执行 node(i) 方法
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;  // 每次都要从头/尾遍历
}

Node<E> node(int index) {
    // 每次调用都要遍历 O(n)
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        // ...
    }
}

2)增强 for 循环 / Iterator

java 复制代码
// 方式1:增强for(底层也是Iterator)
for (Integer num : linkedList2) {
    System.out.print(num + " ");
}

// 方式2:显式使用Iterator
Iterator<Integer> it = linkedList2.iterator();
while (it.hasNext()) {
    System.out.print(it.next() + " ");
}

Iterator 源码分析

java 复制代码
// LinkedList 的 iterator() 方法
public Iterator<E> iterator() {
    return listIterator();  // 返回 ListIterator
}

// ListIterator 的实现(LinkedList内部类)
private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned = null;
    private Node<E> next;           // 下一个要返回的节点
    private int nextIndex;          // 下一个节点的索引
    private int expectedModCount = modCount;
    
    // 构造器
    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;        // 移动指针 O(1)
        nextIndex++;
        return lastReturned.item;
    }
}

时间复杂度:O(n) - 只需遍历一次

a)基本遍历(正向)

java 复制代码
ListIterator<Integer> lit = linkedList2.listIterator();
while (lit.hasNext()) {
    System.out.print(lit.next() + " ");
}

b)从指定位置开始遍历

java 复制代码
// 从倒数第二个元素开始正向遍历
ListIterator<Integer> lit2 = linkedList2.listIterator(linkedList2.size() - 2);
while (lit2.hasNext()) {
    System.out.print(lit2.next() + " ");
}

源码分析

java 复制代码
// ListIterator 构造器
ListItr(int index) {
    next = (index == size) ? null : node(index);  // 直接定位到index位置
    nextIndex = index;
}
// 时间复杂度:O(n) 定位一次,后续遍历O(1) per element

c)反向遍历

java 复制代码
// 技巧:从末尾开始,使用 previous() 反向遍历
ListIterator<Integer> lit2 = linkedList2.listIterator(linkedList2.size());
while (lit2.hasPrevious()) {  // 注意是 hasPrevious
    System.out.print(lit2.previous() + " ");
}

previous() 源码

java 复制代码
public E previous() {
    checkForComodification();
    if (!hasPrevious())
        throw new NoSuchElementException();
    
    lastReturned = next = (next == null) ? last : next.prev;  // 移动前驱指针
    nextIndex--;
    return lastReturned.item;
}

public boolean hasPrevious() {
    return nextIndex > 0;
}

d)遍历时添加元素

java 复制代码
ListIterator<Integer> lit = list.listIterator();
while (lit.hasNext()) {
    Integer num = lit.next();
    if (num == 3) {
        lit.add(99);  // 在3后面插入99
    }
}
// 原列表:[1,2,3,4,5] → [1,2,3,99,4,5]

e)遍历时修改元素

java 复制代码
ListIterator<Integer> lit = list.listIterator();
while (lit.hasNext()) {
    Integer num = lit.next();
    if (num == 3) {
        lit.set(100);  // 将3改为100
    }
}
// 原列表:[1,2,3,4,5] → [1,2,100,4,5]

f) 遍历时删除元素

java 复制代码
ListIterator<Integer> lit = list.listIterator();
while (lit.hasNext()) {
    Integer num = lit.next();
    if (num == 3) {
        lit.remove();  // 删除3
    }
}
// 原列表:[1,2,3,4,5] → [1,2,4,5]
遍历方式 时间复杂度 推荐程度 适用场景
普通for + get(index) O(n²) ❌ 不推荐
增强for O(n) ✅ 推荐 只读遍历
Iterator O(n) ✅ 推荐 需要删除元素
ListIterator O(n) ✅ 推荐 需要双向遍历/修改/添加

笔试面试题

陷阱1:在遍历时直接修改列表

java 复制代码
// ❌ 错误:会抛出 ConcurrentModificationException
for (Integer num : list) {
    if (num == 3) {
        list.remove(num);  // 直接修改原列表
    }
}

// ✅ 正确:使用 Iterator
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    if (it.next() == 3) {
        it.remove();  // 安全删除
    }
}
相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔4 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai10 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说10 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json