【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();  // 安全删除
    }
}
相关推荐
837927397@QQ.COM2 小时前
个人理解无界原理
开发语言·前端·javascript
无心水2 小时前
17、Java内存溢出(OOM)避坑指南:三个典型案例深度解析
java·开发语言·后端·python·架构·java.time·java时间处理
冰暮流星2 小时前
javascript之Dom查询操作1
java·前端·javascript
东离与糖宝2 小时前
Spring AI RAG生产方案:Java对接Gemma 4构建企业知识库
java·人工智能
却话巴山夜雨时i2 小时前
互联网大厂Java面试场景:从Spring到微服务的逐层提问
java·数据库·spring·微服务·日志·性能监控
小江的记录本2 小时前
【Docker】Docker系统性知识体系与命令大全(镜像、容器、数据卷、网络、仓库)
java·网络·spring boot·spring·docker·容器·eureka
广州灵眸科技有限公司2 小时前
瑞芯微(EASY EAI)RV1126B 人脸98关键点算法识别
开发语言·科技·嵌入式硬件·物联网·算法·php
花千树-0102 小时前
JMeter 入门与进阶指南:从零开始构建你的压测环境
java·spring boot·jmeter·性能优化·压力测试·可用性测试
浅月流苏2 小时前
Claude Code安装以及idea集成Claude Code的使用教程(基础篇)
java·ai编程·claude code