LinkedList 源码分析
LinkedList 源码分析
1. LinkedList 概述
1.1 List 是什么?
List
接口是 Java 集合框架中的一个重要组成部分,作为 Collection
的子接口,它定义了一种有序集合(也称为序列)。实现该接口的类(如 ArrayList
和 LinkedList
)可以通过元素的整数索引精确控制元素的插入位置,支持重复元素的存储。
1.2 LinkedList 是什么?
LinkedList
是一个基于双向链表实现的集合。它不仅支持基本的列表操作,还可以被用作堆栈、队列或双端队列。其主要特点包括:
- 查询速度较慢(O(n))
- 插入和删除操作速度较快(O(1))
- 线程不安全
- 效率高
2. 核心源码分析
2.1 类声明
LinkedList
类的声明如下:
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
// ...
}
- Deque:实现了
Deque
接口,使得LinkedList
具备队列特性。 - Cloneable:支持克隆功能。
- Serializable:支持序列化。
2.2 成员变量
LinkedList
的主要成员包括:
transient Node<E> first; // 链表的头节点
transient Node<E> last; // 链表的尾节点
transient int size; // 链表的大小
2.3 内部私有类 Node
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;
}
}
2.4 构造方法
LinkedList
提供多个构造方法,以便用户创建空链表或根据集合初始化链表。
/**
* 无参构造
*/
public LinkedList() {
}
/**
* 带参构造,创建一个包含集合 c 的 LinkedList
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
2.5 添加方法
2.5.1 add(E e)
向链表添加元素的核心方法是 linkLast
:
public void add(E e) {
linkLast(e);
}
private 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; // 否则,将旧尾节点的 next 指向新节点
}
size++;
}
2.5.2 add(int index, E element)
在指定索引处添加元素:
public void add(int index, E element) {
Node<E> node = node(index);
linkBefore(element, node);
}
private 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++;
}
2.5.3 addAll(Collection<? extends E> c)
添加集合:将集合 c
中的所有元素添加到链表的末尾。
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) // 如果集合为空,直接返回 false
return false;
Node<E> pred, succ; // 定义前驱和后继节点
if (index == size) { // 如果在链表末尾插入
succ = null; // 后继节点为 null
pred = last; // 前驱节点为当前最后一个节点
} else { // 在中间位置插入
succ = node(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; // 前驱节点的 next 指向新节点
pred = newNode; // 更新前驱节点为新节点
}
// 如果后继节点为 null,更新尾结点
if (succ == null) {
last = pred; // 将尾结点更新为新插入的最后一个节点
} else { // 否则,连接新节点与后继节点
pred.next = succ; // 新节点的 next 指向后继节点
succ.prev = pred; // 后继节点的 prev 指向新节点
}
size += numNew; // 更新链表大小
modCount++; // 更新修改计数
return true; // 返回 true,表示成功添加元素
}
2.6 获取方法
2.6.1 get(int index)
通过索引获取元素,时间复杂度为 O(n):
public E get(int index) {
return node(index).item; // 获取指定节点的值
}
2.7 删除方法
2.7.1 remove(int index)
根据索引删除元素:
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
private 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) {
first = next; // 更新头节点
} else {
prev.next = next; // 更新前驱节点
}
if (next == null) {
last = prev; // 更新尾节点
} else {
next.prev = prev; // 更新后继节点
}
size--;
return element;
}
2.7.2 remove()
空参删除元素 : 删除的是第一个结点
public E remove() {
return removeFirst();
}
2.7.3 remove(Object o)
根据传递对象删除结点:
- 判断删除元素是否存在
- 找到该元素在链表中位置,并执行
unlink()
方法
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
3. 重点内容分析
底层操作机制(添加元素)
1. 初始化链表
- 头结点 (
first
) 和 尾结点 (last
):- 在链表创建时,
first
和last
都被初始化为null
,表示链表当前是空的。
- 在链表创建时,
2. 添加第一个元素
-
创建新节点:
- 当添加第一个元素时,会创建一个新的节点对象。
- 将
first
和last
都指向这个新节点,因为此时链表中只有一个元素。
3. 添加第二个元素
-
创建第二个节点:
- 当添加第二个元素时,会创建另一个新的节点对象。
- 此时,原有的第一个节点的
next
指针会指向新创建的第二个节点。 last
指针会更新为指向新的第二个节点。
继续添加元素的过程
- 每次添加新节点时:
- 新节点的
prev
指向当前的last
节点(即链表的最后一个节点)。 - 当前
last
节点的next
指向新节点。 - 更新
last
指向新创建的节点。
- 新节点的
4. 性能分析
-
时间复杂度:
- 插入:O(1)(在头或尾),O(n)(在指定位置)
- 删除:O(n)
- 查找:O(n)
-
空间复杂度:O(n),每个节点占用额外的指针空间。
5. 总结
]
继续添加元素的过程
- 每次添加新节点时:
- 新节点的
prev
指向当前的last
节点(即链表的最后一个节点)。 - 当前
last
节点的next
指向新节点。 - 更新
last
指向新创建的节点。
- 新节点的
[外链图片转存中…(img-3jzbmisP-1730257255360)]
4. 性能分析
-
时间复杂度:
- 插入:O(1)(在头或尾),O(n)(在指定位置)
- 删除:O(n)
- 查找:O(n)
-
空间复杂度:O(n),每个节点占用额外的指针空间。
5. 总结
LinkedList
提供了一种灵活且动态的数据存储方式,适合频繁的插入和删除操作。虽然查询效率较低,但在特定场景下,它比 ArrayList
更具优势。理解 LinkedList
的内部实现和操作机制,有助于更好地选择合适的数据结构以应对不同的编程需求。
原文地址:https://blog.csdn.net/PQ781826/article/details/143359542
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!