LinkedList源码分析
概述
LinkedList体系图
从体系图中我们可以看出来,LinkeList不仅有List的特点还有Queue队列的特点。
LinkedList实现了Serializable和Cloneable说明LinkedList可以序列化和克隆

LinkedList特点:
LinkedList的底层数据接口是双向链表
LinkedList是线程不安全的
LinkedList成员变量
LinkedList共有三个成员变量

size:LinkedList中的节点(Node)个数
first:LinkedList底层链表的头结点
last:LinkedList底层链表的尾结点
Node的结构
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构造方法
LinkedList共有两个构造方法
-
空参构造方法
public LinkedList() { } -
带参构造方法
public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
带参的构造方法
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
可以传进来一个集合,然后按照集合中元素的顺序添加到LinkedList的尾部
addAll(c)方法
可以看到实际上是调用了addAll(size, c)这个方法
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
addAll(size, c)方法
public boolean addAll(int index, Collection<? extends E> c) {
// 判断index是否越界
checkPositionIndex(index);
// 将要添加到链表的集合转化为Object[]数组
Object[] a = c.toArray();
// 数组长度
int numNew = a.length;
// 如果集合的个数是0,直接返回false
if (numNew == 0)
return false;
// 定义两个节点,pred链表中index位置的节点的前驱节点,succ表示链表中index位置的节点
Node<E> pred, succ;
// 如果index == size,示当前链表还是空的,表示在链表尾部插入,否则,在链表内部插入
if (index == size) {
// last的后继节点自然是空
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
// 循环将a中的元素添加到链表中
for (Object o : a) {
// 向下转型
@SuppressWarnings("unchecked") E e = (E) o;
// 封装节点
Node<E> newNode = new Node<>(pred, e, null);
// pred = null,表示链表还是空的
if (pred == null)
// 让头结点等于a中的第一个元素
first = newNode;
else
// 如果不是空的话,将newNode作为pred的后继节点
pred.next = newNode;
// 更新pred的值,以便于在链表中添加后续a中的元素
pred = newNode;
}
// 到这,a中的元素就以链表的中的节点串联起来了(也和index前边的节点串联起来了),但是还没有真正的上链,下边的操作就是真正的上链
if (succ == null) {
// succ == null,表示的就是之前的链表是空的,只要让last = pred即可
last = pred;
} else {
// 链表不是空的,得让链表完整下来
pred.next = succ;
succ.prev = pred;
}
// 更新链表中节点的个数
size += numNew;
// 更新链表结构修改的个数
modCount++;
return true;
}
下图表示要向index=1的位置插入两个元素。

执行完循环之后的结构

现在可以看到肯定是不对的,所以要让链表完整,也就是执行pred.next = succ和succ.prev = pred

checkPositionIndex(index)方法
如果越界就会抛出异常
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
isPositionIndex(index)方法
判断是否越界了,没有越界返回true,否则返回false
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
LinkedList的添加方法
看看有什么关于添加的方法

addAll的两个方法上边已经分析过了,下面看其他的方法
add(E):boolean
这个方法默认是向链表的尾部插入元素的,比较简单
public boolean add(E e) {
linkLast(e);
return true;
}
linkLast(e)方法
void linkLast(E e) {
// 保存之前的尾节点
final Node<E> l = last;
// 封装e,并将新节点的pre设置为l=last
final Node<E> newNode = new Node<>(l, e, null);
// 将last执行新节点
last = newNode;
// l = null,说明链表是空的
if (l == null)
// 将newNode赋值给first即可
first = newNode;
else
// 让之前的last的next执行newNode
l.next = newNode;
size++;
modCount++;
}
addFirst(E):void
在链表的头部插入元素
public void addFirst(E e) {
linkFirst(e);
}
linkFirst(e)方法
private void linkFirst(E e) {
// 用f保存之前的first
final Node<E> f = first;
// 封装新节点,并将新节点的next指向f也就是first
final Node<E> newNode = new Node<>(null, e, f);
// 更新first的值
first = newNode;
if (f == null) // 链表是空的话,直接让尾节点也指向新节点
last = newNode;
else
// 不是空的话,让之前的头结点,也就是现在的第二个节点的prev指向新的节点
f.prev = newNode;
size++;
modCount++;
}
addLast(E):void
默认是向链表的尾部添加元素
右下边的代码可以看到和add没区别,都是调用了linkLast(),不再分析
public void addLast(E e) {
linkLast(e);
}
add(int, E):void
向链表中索引为index位置插入元素
public void add(int index, E element) {
// 检查index是否越界,前边分析过,不再分析
checkPositionIndex(index);
// 如果index = size的话,就是向链表中的尾部插入元素,直接调用linkLast(element),上边分析过,不再分析
if (index == size)
linkLast(element);
else
// 向其他位置插入
linkBefore(element, node(index));
}
linkBefore(element, node(index))方法
node(index)表示返回链表中索引为index的节点
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 保存当前节点的前驱节点到pred中
final Node<E> pred = succ.prev;
// 封装新节点,并把新节点的前驱节点指向pred,后继节点指向succ
final Node<E> newNode = new Node<>(pred, e, succ);
// 将succ的前驱节点指向newNode
succ.prev = newNode;
if (pred == null) // 如果要在链表的索引0位置插入元素
// 把newNode当成头节点
first = newNode;
else
// 将newNode当成pred的后继节点
pred.next = newNode;
size++;
modCount++;
}
想index=1的位置插入
初始状态

创建了newNode之后,我的图好抽象

执行了succ.prev = newNode;之后

执行了pred.next = newNode之后,就将newNode插入了链表里

node(index)方法
这个方法就体现出了双向链表比单向链表的好处,插入删除的时候效率要高
Node<E> node(int index) {
// assert isElementIndex(index);因为前边checkPositionIndex(index)已经验证过了,所以这里不需要
// private boolean isElementIndex(int index) {
// return index >= 0 && index < size;
// }
// 将链表分为前半部分和后半部分,通过index和size/2的大小判断是从前半部分找还是后半部分找
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;
}
}
LinkedList的删除方法
看看有什么关于删除的方法

remove():E
默认删除的是第一个节点
public E remove() {
return removeFirst();
}
removeFirst()方法
public E removeFirst() {
// 得到第链表的头节点
final Node<E> f = first;
// 如果头节点都是空了,还删啥删呀,直接报错
if (f == null)
throw new NoSuchElementException();
// 不为空
return unlinkFirst(f);
}
unlinkFirst(f)方法
private E unlinkFirst(Node<E> f) {
// 拿到头节点保存的的值
final E element = f.item;
// 将当前头节点的后继节点保存下来
final Node<E> next = f.next;
// 删除头节点
f.item = null;
f.next = null; // help GC
first = next;
if (next == null) // 链表只有一个元素,删除完头节点,链表就是空的了,让last也等于空
last = null;
else
// 不是空的话,让next成为头节点,将原先next.prev指向的头节点置空
next.prev = null;
// 链表数量减一
size--;
// 链表结构改变次数加1
modCount++;
// 返回删除的节点的item
return element;
}
removeFirst():E
通过代码可以看到,和remove()方法一样,不再分析
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
removeLast():E
删除最后一个元素
public E removeLast() {
// 尾节点赋值给l
final Node<E> l = last;
// 要删除的元素是空了,跑异常
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
unlinkLast(l)方法
private E unlinkLast(Node<E> l) {
// 拿到尾节点的item,用来作为返回值
final E element = l.item;
// 保存删除尾节点的前驱节点
final Node<E> prev = l.prev;
// 删除尾结点
l.item = null;
l.prev = null; // help GC
// 让现在链表的尾结点指向原先链表的尾结点的前驱节点
last = prev;
if (prev == null) // 说明原先链表只有一个节点。删除完链表就空了
first = null;
else // 原先链表不只一个节点,将现在尾结点的next置空,之前是指着原先链表的尾结点的
prev.next = null;
// 链表元素减一
size--;
modCount++;
// 返回item
return element;
}
remove(int):E
删除指定位置的元素
public E remove(int index) {
// 检查是否越界,前边分析过,不再分析
checkElementIndex(index);
return unlink(node(index));
}
unlink(node(index))方法
E unlink(Node<E> x) {
// assert x != null;
// 删除节点的item
final E element = x.item;
// 删除节点的前驱节点
final Node<E> next = x.next;
// 删除节点的后继节点
final Node<E> prev = x.prev;
if (prev == null) { // prev是空,说明删除的节点是头节点
first = next; // first指向next,现在next是头节点
} else {
// 将删除节点的前驱节点的后继节点指向删除节点的后继节点
prev.next = next;
// 再将删除节点的前驱节点置空
x.prev = null;
}
if (next == null) { // prev是空,说明删除的节点是尾节点
last = prev; // last指向prev,现在prev是尾结点
} else {
// 将删除节点的后继节点的前驱节点指向删除节点的前驱节点
next.prev = prev;
// 将删除节点的的后继节点置空
x.next = null;
}
// 将删除节点的item置空,这样这个node才算真正空,等待垃圾回收
x.item = null;
// 链表节点个数减一
size--;
modCount++;
// 返回值
return element;
}
删除是中间节点的情况

remove(Object):boolean
删除指定item的节点
public boolean remove(Object o) {
if (o == null) { // 删除节点的item是空
// 遍历链表
for (Node<E> x = first; x != null; x = x.next) {
// 找到的话,就调用上边分析过的unlink()方法,找到就会停止,不会删除全部
if (x.item == null) {
unlink(x);
return true;
}
}
} else { // 删除节点的item不是空
for (Node<E> x = first; x != null; x = x.next) {
// 用equals判断,如果相等,就调用unlink()进行删除
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
// 没找到就返回false
return false;
}
removeFirstOccurrance(Object):boolean
实际上调用的是remove(Object):boolean,分析过了,不再分析
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
removeLastOccurrance(Object):boolean
和remove(Object):boolean基本一样,就是for循环从last开始
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
clear():void
public void clear() {
// 遍历链表
for (Node<E> x = first; x != null; ) {
// 保存当前节点的下一个,如果不保存的话,后续的节点没有办法删除
Node<E> next = x.next;
// Node三个属性置空,等待GC
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
// 链表都空了,LinkedList的first和last也是空了
first = last = null;
// 节点的个数置0
size = 0;
modCount++;
}
LinkedList的根据索引查询节点的方法
先来看有些什么查询的方法

get(int):E
获取指定索引位置的元素值,这中get的方式的查询效率其实是不高的,从node(index)的源码就可以知道,要经过遍历,虽然LinkedList采用了双向链表,但是还是要遍历一部分。不如ArrayList的get(int)方法,O(1)的时间复杂度。所以,如果经常要查找到的话,还是考虑用ArrayList
public E get(int index) {
// 检查index是否越界
checkElementIndex(index);
// 不越界的话,就调用node(index)找到当前节点,然后再返回item的值
return node(index).item;
}
getFirst():E
拿到链表第一个节点的值的item属性,判断first不为空,然后直接返回item的值
public E getFirst() {
// 将first赋值给f
final Node<E> f = first;
// 判断是不是null
if (f == null)
throw new NoSuchElementException();
// 返回链表第一个元素的值
return f.item;
}
getLast():E
返回链表最后一个节点的item属性
首先将last值赋值给l,l不空,直接返回l的item属性
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
LinkedList修改节点的方法
只有一个set()方法

set(int, E):E
public E set(int index, E element) {
// 检查要修改的索引是否越界
checkElementIndex(index);
// 拿到index在链表中代表的节点
Node<E> x = node(index);
// 拿到现在节点的item的值
E oldVal = x.item;
// 再将新的element赋值给index所代表的的节点
x.item = element;
// 再返回oldVal的值
return oldVal;
}
LinkedList根据item返回节点index

indexOf(Object):int
从链表的前边开始遍历,找到Object出现的第一个位置的索引
public int indexOf(Object o) {
// 定义存储索引的变量index
int index = 0;
if (o == null) { // 还是o是否是空,如果是空的话,要用== 来比较,不是空的话,要用equals来比较
// 循环链表,找到就返回
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
// o不是空,遍历链表,用equals判断
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
// 有找到就返回-1
return -1;
}
lastIndexOf(Object):int
从链表的后边开始遍历,找到Object出现的第一个位置的索引
public int lastIndexOf(Object o) {
// 因为是从后向前找嘛,index=size,如果index = size - 1的话,代码的结构和上边的代码基本就不会变了
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
// 先减一,这样index的值才代表当前正在遍历的节点
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;
}
}
// 没有找到就返回-1
return -1;
}
LinkedList作为双向队列
再回过头来看LinkedList的继承体系,可以看到LinkedList是实现了Deque的,二Deque继承了Queue,所以LinkedList既实现了Queue也实现了Deque

Queue接口中定义的方法

-
第一组:都是向队列的尾部添加元素。对于一个有限队列来说,队列满的话,再往里添加元素,add()会抛出异常,offer()只是返回false
add(E):boolean
offer(E):boolean
-
第二组:都是查看队列头部的元素,但是element当队列是空的话,会抛出异常。peek()会返回null
element():E
peek():E
-
第三组:都是弹出头部元素,但是remove()当队列是空的话,会抛出异常。poll()会返回null
remove():E
poll():E
Deque接口中定义的方法

Deque双端队列
实现了这个接口的类,既可以作为队列使用,也可以作为栈使用(LinkedList)
Deque提供的方法,图片来源https://juejin.cn/post/6844903586095169549

我们通过下边的表格来对照下双端队列是如何实现队列操作的,值得注意的是 Deque 实现了 Queue,所以 Queue 所有的方法 Deque 都有,下面比较的是Deque区别 Queue 的方法:

LinkedList作为队列实现的Queue和Deque方法的不同
添加元素的方法
// Queue的方法
public boolean add(E e) {
linkLast(e);
return true;
}
// Deque中的方法
public void addLast(E e) {
linkLast(e);
}
// Queue的实现
public boolean offer(E e) {
return add(e);
}
// Deque的实现
public boolean offerLast(E e) {
addLast(e);
return true;
}
删除头元素的方法
所以,在删除头节点的方法中,Queue和Deque都是一样的
// Queue中的方法,会抛出异常
public E remove() {
return removeFirst();
}
// 会抛出异常
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
// 如果头节点是空的话,会返回null。Queue
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
// 和poll一模一样
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
获取头节点元素的方法
所以,在获取头节点的方法中,Queue和Deque都是一样的
// Queue
public E element() {
return getFirst();
}
// Deque,可以看出来element其实就是调用的getFirst(),头元素为空的话,抛出异常
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
// Queue,如果是空的直接返回空,不抛出异常
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
// 和peek()一模一样
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
LinkedList作为栈

push(),直接调用addFirst(e)
public void push(E e) {
addFirst(e);
}
pop()直接调用removeFirst(e)
public E pop() {
return removeFirst();
}
peek()直接调用peekFirst()
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
最主要的方法其实还是在作为List实现的

浙公网安备 33010602011771号