Java 集合 --LinkedList 源码可视化
合节点保存的都是对象的引用,而非具体值,文中案例仅仅为了方便实现原理的演示。
📝1. 底层数据结构
LinkedList
基于 双向链表 实现,内部通过 Node<E>
节点相互连接:
在 LinkedList
类中,通过以下字段维护链表头尾:
first
始终指向第一个节点,last
始终指向最后一个节点。

双向链表支持从头或尾进行 快速插入/删除。但不能像数组一样,根据索引下标进行地址计算,所以不支持 随机访问,通过索引访问需要 遍历节点。
1.1. LinkedList 特性
顺序存储:维护插入顺序,但本质为链式结构;
节点插入/删除:在任意位置插入或删除均为 O(1)(找到节点后);
访问效率:按索引访问需 O(n),因为需要从头或尾遍历;
支持 null 元素,保存的是对象引用。
🚀2. 元素插入(增)
LinkedList
提供四种常用插入方式:

2.1. linkFirst 和 linkLast 核心源码
link
指的是连接节点的意思,first
和last
分别表示从头部连接(插入)节点和从尾部连接(插入)节点。所以,在学习插入元素时,需要相对这两块源码熟悉。
创建新节点并调整前后指针;
首次插入时
first
和last
同时指向该节点;size++
、modCount++
支持 fail-fast。
在尾部插入调用linkLast
方法即可,源码如下
尾部插入动画效果

在头部插入调用linkFirst
方法即可,源码如下
头部插入动画效果

2.2. 在中间位置插入
node(index)
根据索引返回目标节点,内部会判断从头或尾遍历更快;连接新节点至前后节点。
在中间插入的动画效果

2.3. node(index)检索算法(重要)
链表数据结构无法像数组那样,可以直接根据索引下标和首地址来计算目标元素地址,只能从头部first
节点或尾部tail
节点一步一步的遍历到目标位置,从而获取到目标节点。
源码如下,(具体检索过程的动图可到修改元素章节感受)
法解析:
将链表逻辑分为两段:前半段
[0, size/2)
和后半段[size/2, size)
;若目标索引位于前半段,则从
first
开始,依次沿next
找到第index
个节点;若在后半段,则从
last
开始,沿prev
向前遍历直到到达;这种折半遍历相当于对链表元素位置进行“裁剪”,最坏需要遍历
size/2
步,平均遍历size/4
步,但仍为 O(n)。
适用场景:在随机访问时,通过双向遍历能显著减少平均查找距离,提高链表访问效率。
📍3. 移除元素(删)
LinkedList
提供多种删除方式:

3.1. unlink 核心源码
unlink
指的是断开节点连接,删除的本质是断开节点连接,使其前后节点重新连接,此时删除的节点在内存中就成了游离状态,后续会被 GC 清理回收。
调整前后节点指针,再断开目标节点;
GC 清理旧引用;
size--
、modCount++
。
3.2. 删除头部节点
删除头部节点并返回的源码:
删除过程效果图:

3.3. 删除尾部节点
删除尾部节点并返回的源码:
删除过程效果图:

3.4. 删除指定索引节点
删除指定索引节点并返回的源码:
删除过程效果图:

📌4. 修改元素(改)
set(int index, E element)
:找到指定索引节点后直接覆盖
检索算法还是node(index)
,找到节点后直接修改节点内容的指向。
效果如图:将索引为 2 的-46
改为88

如果在-46
元素后面还多一个元素呢?效果如下:

🎯5. 获取和检索元素(查)
5.1. 获取元素
通过 node(index)
遍历链表查找节点,复杂度 O(n)。
5.2. 检索元素
contains(Object o)
/indexOf
/lastIndexOf
:同ArrayList
,但比较的是链表节点的item
,需线性遍历,时间复杂度 O(n);
6. LinkedList 的迭代器
LinkedList
实现了 双向迭代:
Iterator<E> iterator()
返回基于头到尾的迭代器;ListIterator<E> listIterator()
支持从任意位置开始双向遍历。
核心与 ArrayList
类似,使用 modCount
检测并发修改,next()
/previous()
拿到节点后返回 item
。
7. 并发安全问题
LinkedList
同样线程不安全;
多线程操作同一实例时需自行同步:
Collections.synchronizedList(new LinkedList<>())
使用
CopyOnWriteArrayList
不适用于链表,可改用ConcurrentLinkedDeque
作为替代;
8. 时间复杂度汇总

9. 总结
LinkedList
适合频繁插入/删除的场景,尤其是在头尾操作;不适合随机访问,大规模 get/set
性能较差;在并发场景下需要显式同步或选用合适的并发链表实现;
根据场景选择:
读多写少、随机访问 用
ArrayList
;插入/删除频繁、双端操作 用
LinkedList
。
文章转载自:渊渟岳
评论