LinkedList源码学习
一.LinkedList与ArrayList的异同
1.相同点:
- ArrayList、LinkedList都是单列集合中List接口的实现类,直接或间接继承了AbstractList类
- 他们都是存取、允许重复、可有多个null值,插入有序
- 都是线程不安全的
2.不同点:
-
ArrayList的实现是基于动态数组,在内存中分配连续的空间
LinkedList的底层数据结构是双向链表
-
ArrayList查询快,增删慢
LinkedList查询慢、增删快
-
LinkedList继承了Deque接口,所以LinkedList还实现了Deque接口的特有方法。
-
在增加操作时,ArrayList通过System.arraycopy完成批量增加的
LinkedList通过依次加入结点做到的
二.源码部分
1.基本属性
**AbstractSequentialList 继承自 AbstractList,是 LinkedList 的父类,是 List 接口 的简化版实现。简化在 AbstractSequentialList 只支持按次序访问,而不像 AbstractList 那样支持随机访问。
List 的数据结构就是一个序列,存储内容时直接在内存中开辟一块连续的空间,然后将空间地址与索引对应。
Deque 表示双端队列。双端队列是在两端都可以进行插入和删除的队列。Deque是一个比Stack和Queue功能更强大的接口,它同时实现了栈和队列的功能。ArrayDeque和LinkeList实现了Deque接口。
Cloneable接口 是一个标记接口,也就是没有任何内容
Serializable接口 是启用其序列化功能的接口。实现java.io.Serializable 接口的类是可序列化的。没有实现此接口的类将不能使它们的任意状态被序列化或逆序列化。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable {
//transient关键字为类型修饰符,英文本意为“短暂的”,在对象序列化过程中,标记了transient的变量不会被序列化。
transient int size;
//头节点
transient LinkedList.Node<E> first;
//尾节点
transient LinkedList.Node<E> last;
//数字后面加上的L表示这是一个long值。 通过这种方式来解决不同的版本之间的串行话问题
private static final long serialVersionUID = 876323262645176354L;
2.Node
//私有静态内部类
private static class Node<E> {
//节点值
E item;
//后继结点
LinkedList.Node<E> next;
//前驱结点
LinkedList.Node<E> prev;
//有参构造方法
Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
3.构造函数
/**
* 构造函数
*1.无参构造函数LinkedList()
* 2.有参构造函数
* 2.1LinkedList(Collection<? extends E> c)
* 2.2linkFirst(E e)
*/
//无参构造,size为0
public LinkedList() {
this.size = 0;
}
//将已有元素的集合Collection 的实例添加到 LinkedList 中,调用的是 addAll() 方法
public LinkedList(Collection<? extends E> c) {
this();
this.addAll(c);
}
4.add方法
- boolean addAll(Collection<? extends E> c)
//将一个集合的所有元素添加到链表后面,返回是否成功,成功为 true,失败为 false。
public boolean addAll(Collection<? extends E> c) {
return this.addAll(this.size, c);
}
-
boolean addAll(int index, Collection<? extends E> c)
//将一个集合的所有元素添加到链表的指定位置后面,返回是否成功,成功为 true,失败为 false。 public boolean addAll(int index, Collection<? extends E> c) { //调用方法,不正确则将抛出异常 this.checkPositionIndex(index); //将list直接转为Object[] 数组 Object[] a = c.toArray(); //数组a的长度 int numNew = a.length; //如果长度为0,则返回失败 if (numNew == 0) { return false; } else { //要添加位置的前驱结点 LinkedList.Node pred; //添加位置的结点,index号 LinkedList.Node succ; //在链表后面插入结点 if (index == this.size) { succ = null; pred = this.last; } else { //node()方法返回index结点 succ = this.node(index); //前驱结点则为succ的前驱结点 pred = succ.prev; } Object[] var7 = a; int var8 = a.length; //遍历a.length次数组 for(int var9 = 0; var9 < var8; ++var9) { //将数组中的值赋给o Object o = var7[var9]; //通过有参构造函数的方法新建结点,前驱结点为:pred, 值为o, 后继结点为null LinkedList.Node<E> newNode = new LinkedList.Node(pred, o, (LinkedList.Node)null); //如果添加位置为第一个位置 if (pred == null) { //直接将first指向新的结点 this.first = newNode; } else { //如果不为第一个位置,则将前驱结点的next指向新结点 pred.next = newNode; } //pred指向新的结点,等待下个结点顺序插入 pred = newNode; } //如果插入位置为尾节点 if (succ == null) { //将last指向目前链表的尾节点 this.last = pred; } else { //将本来在index位置的结点添加在链表上 pred.next = succ; succ.prev = pred; } //链表的大小加上集合的长度 this.size += numNew; //增加链表修改次数 ++this.modCount; return true; } }
5.remove方法
- public E removeFirst()
//删除并返回第一个元素。
public E removeFirst() {
//取到第一个结点的
LinkedList.Node<E> f = this.first;
//如果为空,则抛出异常
if (f == null) {
throw new NoSuchElementException();
} else {
//删除第一个结点
return this.unlinkFirst(f);
}
}
//删除首结点
private E unlinkFirst(LinkedList.Node<E> f) {
//先将数据保存起来
E element = f.item;
//先将首结点的后一个结点保存起来
LinkedList.Node<E> next = f.next;
//f = null
f.item = null;
f.next = null;
//首结点变成要删除结点的后一个结点
this.first = next;
//如果只有一个结点,即首结点没有后继节点,则将
if (next == null) {
this.last = null;
} else {
//next结点则为新的首结点
next.prev = null;
}
//size - 1
--this.size;
//增加链表修改次数
++this.modCount;
//返回首结点的数值
return element;
}
public boolean remove(Object o)
//删除某一元素,返回是否成功,成功为 true,失败为 false。
public boolean remove(Object o) {
LinkedList.Node x;
//如果实参为null,则将值为null的都遍历删除
if (o == null) {
for(x = this.first; x != null; x = x.next) {
if (x.item == null) {
this.unlink(x);
return true;
}
}
} else {
for(x = this.first; x != null; x = x.next) {
if (o.equals(x.item)) {
this.unlink(x);
return true;
}
}
}
return false;
}
//删除参数结点
E unlink(LinkedList.Node<E> x) {
E element = x.item;
//保存前驱和后继结点
LinkedList.Node<E> next = x.next;
LinkedList.Node<E> prev = x.prev;
//如果要删除的结点为首结点
if (prev == null) {
this.first = next;
} else {
//若删除结点有后继结点,则将其指向要删除结点的后继结点
prev.next = next;
//将x设孔
x.prev = null;
}
//如若要删除的结点为最后一个结点
if (next == null) {
//last指针指向删除结点的前一个结点
this.last = prev;
} else {
//否则,将要删除结点的后继结点的前驱结点指向要删除结点的前驱结点
next.prev = prev;
//将x设孔
x.next = null;
}
//将x设孔
x.item = null;
//size也 - 1
--this.size;
++this.modCount;
//返回被删除结点的数据
return element;
}
- public E remove(int index)
//删除指定位置的元素。
public E remove(int index) {
//同样的index检查
this.checkElementIndex(index);
//先用node方法找到目标结点,传参到unlink方法中删除
return this.unlink(this.node(index));
}
6.get、set方法
- public E get(int index)
//返回指定位置的元素。
public E get(int index) {
//与addall相同,添加指定位置时就检查是否在范围内
this.checkElementIndex(index);
//调用node方法,返回index结点的存储值
return this.node(index).item;
}
- public E getFirst()
//返回第一个元素。
public E getFirst() {
//获取首结点
LinkedList.Node<E> f = this.first;
//若为空,则抛出异常
if (f == null) {
throw new NoSuchElementException();
} else {
//返回数据
return f.item;
}
}
-
public E set(int index, E element)
//设置指定位置的元素 public E set(int index, E element) { //检查index是否合法 this.checkElementIndex(index); //新建临时结点 x 存储index结点 LinkedList.Node<E> x = this.node(index); //将旧的index结点的值保存起来 E oldVal = x.item; //重新为其赋值 x.item = element; //返回旧的值 return oldVal; }
7.LinkedList.Node node(int index)方法
//为add,get等操作的提供了查找并返回结点的功能
LinkedList.Node<E> node(int index) {
LinkedList.Node x;
int i;
//位移运算符大于小于操作
//当要查找的index小于size的一半,x指向首结点
if (index < this.size >> 1) {
x = this.first;
//遍历index - 1次,找到index的位置
for(i = 0; i < index; ++i) {
x = x.next;
}
//返回index结点
return x;
} else {
//如果index的值在链表的后半部
//x = 尾结点,从后面开始遍历
x = this.last;
//遍历到index的前一个结点
for(i = this.size - 1; i > index; --i) {
x = x.prev;
}
return x;
}
}
三.总结
- 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
- 头尾和已知节点的插入和删除时间复杂度都是o(1)
- 在增加,查找,删除操作的时候通过node(index)找到并返回目标结点
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构