[Java 集合] LinkedList源码分析
源码基于jdk 11
LinkedList是一个以双向链表实现的List,它除了作为List使用,还可以作为双端队列或者栈来使用
一、属性
//元素的个数
transient int size;
//头节点
transient LinkedList.Node<E> first;
//尾节点
transient LinkedList.Node<E> last;
private static final long serialVersionUID = 876323262645176354L;
简单明了:头节点,尾节点,节点个数
二、主要内部类
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;
}
}
每个节点都包含:前节点、当前节点元素、后节点
三、构造方法
//空参构造方法
public LinkedList() {
this.size = 0;
}
无参构造函数,初始化size=0
public LinkedList(Collection<? extends E> c) {
//先调用空参构造,创建一个size为0的LindList
this();
this.addAll(c);
}
在构造函数LinkedList(Collection<? extends E> c)中,
首先通过无参构造函数,创建一个size为0的LinkList集合,
然后通过addAll方法将传入的集合c添加到当前集合的后面。
addAll(Collection<? extends E> c) 作为成员方法,下文中有详细分析
四、常用方法
接下来,就是增删改查了
增
addFirst(E e)
在LinkList集合头部插入元素
public void addFirst(E e) {
this.linkFirst(e);
}
private void linkFirst(E e) {
//获取头部节点
LinkedList.Node<E> f = this.first;
//创建新节点,新节点的前置节点是null,存储元素是e,后置节点是f(头部节点)
LinkedList.Node<E> newNode = new LinkedList.Node((LinkedList.Node)null, e, f);
//将新节点置为头部节点
this.first = newNode;
//如果原头部节点为null,也就是说,添加的是集合的第一个元素
if (f == null) {
//将新节点置为尾部节点,此时,因为只有一个节点,所以此节点既是头部节点也是尾部节点
this.last = newNode;
} else {
//当原头部节点不为null时,则将其前节点的地址prev指向新节点(原prev指向的null)
f.prev = newNode;
}
//节点个数自增
++this.size;
//修改次数自增
++this.modCount;
}
- 获取集合的头部节点;
- 构建一个新的节点:前置节点指向null,存储元素设置为需要插入的元素,后置节点指向头部节点;
- 将新构建的节点设置为头部节点;
- 判断插入的是否时集合的第一个元素
– 是第一个元素,将新节点同时设置成尾部节点
– 不是第一个元素,将原头部节点的前置节点地址指向新节点 - 节点个数自增
- 修改次数自增
addLast(E e)
在LinkList集合尾部插入元素
public void addLast(E e) {
this.linkLast(e);
}
void linkLast(E e) {
LinkedList.Node<E> l = this.last;
LinkedList.Node<E> newNode = new LinkedList.Node(l, e, (LinkedList.Node)null);
this.last = newNode;
if (l == null) {
this.first = newNode;
} else {
l.next = newNode;
}
++this.size;
++this.modCount;
}
- 获取集合的尾部节点;
- 构建一个新的节点:前置节点指向尾部节点,存储元素设置为需要插入的元素,后置节点指向null;
- 将新构建的节点设置为尾部节点;
- 判断插入的是否时集合的第一个元素
– 是第一个元素,将新节点同时设置成头部节点
– 不是第一个元素,将原尾部节点的后置节点地址指向新节点 - 节点个数自增
- 修改次数自增
add(E e)
向LinkList集合添加元素的方法
public boolean add(E e) {
//可以看到,默认就是在LinkList集合尾部插入元素
this.linkLast(e);
return true;
}
add(int index, E element)
在指定索引位置插入元素
public void add(int index, E element) {
//判断索引是否越界
this.checkPositionIndex(index);
//是否向链表末端插入一个元素(当前节点最大索引值为size-1)
if (index == this.size) {
this.linkLast(element);
} else {
//不是在集合尾部添加元素,调用linkBefore方法,传入要添加的元素和指定索引处的节点
this.linkBefore(element, this.node(index));
}
}
void linkBefore(E e, LinkedList.Node<E> succ) {
//获取[原节点的前置节点]
LinkedList.Node<E> pred = succ.prev;
//构建一个新的节点,新节点的前置节点指向[原节点的前置节点],存储元素设置为需要插入的元素,后置节点指向原节点
LinkedList.Node<E> newNode = new LinkedList.Node(pred, e, succ);
//再将[原节点的前置节点]指向新节点
succ.prev = newNode;
//判断[原节点的前置节点]是否为null
if (pred == null) {
//如果[原节点的前置节点]为空,说明它是头节点。此时,将新插入的节点设置为头部节点
this.first = newNode;
} else {
//将[原节点的前置节点] 的后置节点指向新节点(好拗口啊。。)
pred.next = newNode;
}
++this.size;
++this.modCount;
}
- 检查索引是否越界;
- 是否向集合末端添加元素;
- 是,在结合末端插入元素(同addLast方法)
- 否
- 获取[原节点的前置节点]
- 构建一个新的节点,新节点的前置节点指向[原节点的前置节点],存储元素设置为需要插入的元素,后置节点指向原节点
- 将[原节点的前置节点]指向新节点
- 判断[原节点的前置节点]是否为null
– 为null,说明它是头节点。此时,将新插入的节点设置为头部节点
– 不为null,将[原节点的前置节点] 的后置节点指向新节点 - 节点个数自增
- 修改次数自增
addAll(Collection<? extends E> c)
向LinkList中添加集合
public boolean addAll(Collection<? extends E> c) {
return this.addAll(this.size, c);
}
直接调用了addAll(int index, Collection<? extends E> c)方法
addAll(int index, Collection<? extends E> c)
在LinkList指定索引位置插入集合
public boolean addAll(int index, Collection<? extends E> c) {
//检查索引是否越界
this.checkPositionIndex(index);
//将需要添加的集合转成数组
Object[] a = c.toArray();
//判断添加的集合是否为空
int numNew = a.length;
if (numNew == 0) {
//添加的集合为空,就不用添加了
return false;
} else {
//pred节点是关键
LinkedList.Node pred;
//index(指定索引)处的节点
LinkedList.Node succ;
//判断插入位置是否在集合末尾
if (index == this.size) {
//在原集合尾部插入集合,将pred指向原集合的尾节点
succ = null;
//pred节点指向原集合的尾节点
pred = this.last;
} else {
//不是在尾部插入,取索引处的节点
succ = this.node(index);
//pred节点指向索引处节点的前置节点
pred = succ.prev;
}
//需要添加数组
Object[] var7 = a;
//数组的长度
int var8 = a.length;
//遍历数组,利用数组里的元素构建一个链表
for(int var9 = 0; var9 < var8; ++var9) {
//获取元素
Object o = var7[var9];
//构建新节点newNode:前置节点为pred,元素为当前获取的元素,后置节点为null
//newNode 的前置节点指向pred
LinkedList.Node<E> newNode = new LinkedList.Node(pred, o, (LinkedList.Node)null);
//判断是否是头节点
if (pred == null) {
//如果前置节点为null,则将新构建的节点设置为头节点
this.first = newNode;
} else {
//前置节点不为null,则将pred节点的后置节点指向新构建的节点
//pred 的后置节点指向newNode
pred.next = newNode;
}
//pred指向新节点
pred = newNode;
}
//判断指定索引处的节点是否为null
if (succ == null) {
//是尾节点的后置节点,将尾节点指向pred节点
this.last = pred;
} else {
//不是空集合,将pred节点的后置节点指向索引处节点
pred.next = succ;
//再将索引处节点的前置节点指向pred节点
succ.prev = pred;
}
//集合元素个数更新
this.size += numNew;
//修改次数自增
++this.modCount;
return true;
}
}
- 检查索引是否越界;
- 将需要添加的集合c转成数组a;
- 检查数组a是否不为空;(为空就不添加了,后面只讨论不为空的情况)
- 定义临时节点pred和索引处节点succ;
- 判断索引指向的节点是否是集合的尾节点的后置节点(尾节点的后置节点指向null)
– 是,那么此时索引处节点succ指向null ,将pred节点指向当前集合的尾节点。
– 否,获取索引处节点succ,并将pred节点置为succ节点的前置节点。 - 遍历数组,在pred节点后面,利用数组里的元素构建一个链表(从前往后构建链表)
- 判断pred节点是否为null
– 是,说明原集合为空,则将新构建的节点置为头节点
– 否,则将新节点放在pred后面 - 更新pred节点,使其指向新节点
- 判断pred节点是否为null
- 判断索引指向的节点是否是集合的尾节点的后置节点(尾节点的后置节点指向null)
– 是尾节点的后置节点,相当于在原链表的末尾插入新链表;
那么就将pred节点(当前表示的是新链表的最后一个节点)置为尾节点
– 不是尾节点的后置节点,相当于在原链表的中间插入新链表;
那么先将新链表的最后一个节点pred 的后置节点指向索引处节点succ,
然后再将索引处节点succ的前置节点指向pred节点 - 更行集合元素个数
- 修改字数自增
删
remove()
默认删除第一个元素
public E remove() {
return this.removeFirst();
}
直接调用removeFirst()方法,下面介绍
removeFirst()
删除LinkList集合中的第一个元素
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.item = null;
f.next = null;
//将第二个节点置为头节点
this.first = next;
//判断第二个节点是否为null
if (next == null) {
//如果第二个节点为null,说明集合已经没有元素了,将尾节点也置为null
this.last = null;
} else {
//如果第二个节点不为空,那么作为头节点,它的前置节点要指向null
next.prev = null;
}
//删除了一个元素,集合的元素个数要自减
--this.size;
//修改次数要自增
++this.modCount;
//返回被删除的元素
return element;
}
removeLast()
删除LinkList集合中的最后一个元素
public E removeLast() {
//获取链表的尾节点
LinkedList.Node<E> l = this.last;
//检查尾节点是否存在
if (l == null) {
throw new NoSuchElementException();
} else {
return this.unlinkLast(l);
}
}
private E unlinkLast(LinkedList.Node<E> l) {
//获取尾节点的元素
E element = l.item;
//获取倒数第二个节点
LinkedList.Node<E> prev = l.prev;
//将尾节点置空
l.item = null;
l.prev = null;
//将倒数第二个节点置为尾节点
this.last = prev;
//判断倒数第二个节点是否为空
if (prev == null) {
//如果倒数第二个节点为空,说明集合已经没有元素了,将头节点也置为null
this.first = null;
} else {
//如果倒数第二个节点不为空,作为尾节点,它的后置节点要指向null
prev.next = null;
}
//删除了一个元素,集合的元素个数要自减
--this.size;
//修改次数要自增
++this.modCount;
//返回被删除的元素
return element;
}
remove(Object o)
删除LinkList集合中指定的元素
public boolean remove(Object o) {
LinkedList.Node x;
//判断删除的元素是否是null
if (o == null) {
//如果删除的元素是null,顺向遍历链表
for(x = this.first; x != null; x = x.next) {
//判断遍历到的节点中的元素是否为null
if (x.item == null) {
//如果当前节点的元素为null,则调用unlink方法删除此节点
this.unlink(x);
//完成一次删除就结束
return true;
}
}
} else {
//如果删除的元素不是null,顺向遍历链表
for(x = this.first; x != null; x = x.next) {
//判断遍历到的节点中的元素是要删除的元素
if (o.equals(x.item)) {
//如果是要删除的元素,则调用unlink方法删除此节点
this.unlink(x);
//完成一次删除就结束
return true;
}
}
}
//遍历结束都没返回true,说明没有找到,删除失败,返回false
return false;
}
E unlink(LinkedList.Node<E> x) {
//获取要删除节点的元素
E element = x.item;
//分别获取要删除节点的后置节点和前置节点
LinkedList.Node<E> next = x.next;
LinkedList.Node<E> prev = x.prev;
//判断前置节点是否为null
if (prev == null) {
//前置节点为null,说明被删除的是头节点,那么就要将它的后置节点置为头节点
this.first = next;
} else {
//被删除的不是头节点,则将前置节点的next指向被删除节点的后置节点(断开了被删除节点)
prev.next = next;
//同时,将被删除的节点的prev指向null(一后一前,双向指针都断掉了)
x.prev = null;
}
//判断后置节点是否为null
if (next == null) {
//后置节点为null,说明被删除的是尾节点,那么就要将它的前置节点置为尾节点
this.last = prev;
} else {
//被删除的不是尾节点,则将后置节点的prev指向被删除节点的前置节点
next.prev = prev;
//同时,将被删除节点的next指向null
x.next = null;
}
//被删除节点的元素置为null
x.item = null;
//集合元素个数自减
--this.size;
//修改次数自增
++this.modCount;
//返回被删除的元素
return element;
}
remove(int index)
删除指定索引处的元素
public E remove(int index) {
//检查索引是否越界
this.checkElementIndex(index);
//先找到索引处的节点,再调unlink方法传入索引处的节点
return this.unlink(this.node(index));
}
LinkedList.Node<E> node(int index) {
LinkedList.Node x;
int i;
//这里要判断index是处于链表的前半段还是后半段
if (index < this.size >> 1) {
//index位于链表前半段,获取头部节点
x = this.first;
//从前往后遍历,一直到index前面一位的位置
for(i = 0; i < index; ++i) {
x = x.next;
}
//index前面的节点的next指向的就是index处节点
return x;
} else {
//index位于链表后半段,获取尾部节点
x = this.last;
//从后往前遍历,一直到index后面一位的位置
for(i = this.size - 1; i > index; --i) {
x = x.prev;
}
//index后面的节点的prev指向的就是index处节点
return x;
}
}
removeFirstOccurrence(Object o)
删除LinkList中第一次出现的指定元素
public boolean removeFirstOccurrence(Object o) {
return this.remove(o);
}
通过调用 remove(Object o)方法实现(上文有介绍)
removeLastOccurrence(Object o)
删除LinkList中最后一次出现的指定元素
public boolean removeLastOccurrence(Object o) {
LinkedList.Node x;
//判断指定元素是否为null
if (o == null) {
//从后向前遍历链表
for(x = this.last; x != null; x = x.prev) {
//找到元素为null的节点
if (x.item == null) {
//删除该节点,结束循环
this.unlink(x);
return true;
}
}
} else {
//要删除的元素不为null,从后向前,遍历链表
for(x = this.last; x != null; x = x.prev) {
//找到第一个含有指定元素的节点
if (o.equals(x.item)) {
//删除该节点,结束循环
this.unlink(x);
return true;
}
}
}
//集合中不包含该元素,则返回false
return false;
}
改
set(int index, E element)
将指定索引处的节点中元素修改为指定元素
public E set(int index, E element) {
//检查索引是否越界
this.checkElementIndex(index);
//先获取指定索引处的节点
LinkedList.Node<E> x = this.node(index);
//获取该节点原来的元素
E oldVal = x.item;
//将该节点的元素修改为指定的元素
x.item = element;
//返回该节点原来的元素
return oldVal;
}
查
getFirst()
获取LinkList集合中第一个元素
public E getFirst() {
//先获取头节点
LinkedList.Node<E> f = this.first;
if (f == null) {
throw new NoSuchElementException();
} else {
//如果头节点不为null,则取其元素
return f.item;
}
}
getLast()
获取LinkList集合中最后一个元素
public E getLast() {
//先获取尾节点
LinkedList.Node<E> l = this.last;
if (l == null) {
throw new NoSuchElementException();
} else {
//如果尾节点不为null,则取其元素
return l.item;
}
}
get(int index)
获取LinkList集合中指定索引处节点的元素
public E get(int index) {
//检查索引是否越界
this.checkElementIndex(index);
//先获取指定索引处的节点,再获取节点的元素
return this.node(index).item;
}
node(int index)方法在上文remove(int index)方法中有介绍
peek()
取第一个元素
public E peek() {
//获取头节点
LinkedList.Node<E> f = this.first;
//如果节点不为空,取节点元素
return f == null ? null : f.item;
}
peekFirst()
同上
public E peekFirst() {
LinkedList.Node<E> f = this.first;
return f == null ? null : f.item;
}
peekLast()
取最后一个元素
public E peekLast() {
//获取尾节点
LinkedList.Node<E> l = this.last;
//如果尾节点不为空,取节点元素
return l == null ? null : l.item;
}
get方法和peek方法的区别在于:如果集合为空,那么get方法会抛异常,而peek方法返回null
其他方法
pop()
弹栈,获取并删除第一个元素;集合为空时,抛出异常
public E pop() {
return this.removeFirst();
}
poll()
弹栈,获取并删除第一个元素;集合为空时,返回null
public E poll() {
LinkedList.Node<E> f = this.first;
return f == null ? null : this.unlinkFirst(f);
}
pollFirst()
同上
public E pollFirst() {
LinkedList.Node<E> f = this.first;
return f == null ? null : this.unlinkFirst(f);
}
pollLast()
弹栈,获取并删除最后一个元素;集合为空时,返回null
public E pollLast() {
LinkedList.Node<E> l = this.last;
return l == null ? null : this.unlinkLast(l);
}
pop和poll的区别:pop只能单向,poll可以双向
push(E e)
压栈,在集合头部添加元素
public void push(E e) {
this.addFirst(e);
}