LinkedList源码解析
简介
LinkedList 是一个双向链表(内部是 Node 节点)实现的 List,并且还实现了 Deque 接口,它除了作为 List 使用,还可以作为队列
或者栈
来使用。
这样看来,LinkedList 简直就是个全能冠军。当你需要使用栈或者队列时,可以考虑使用 LinkedList,一方面是因为 Java 官方已经声明不建议使用 Stack 类,更遗憾的是,Java 里根本没有一个叫做 Queue 的类(它是个接口名字)。
关于栈或队列,现在的首选是 ArrayDeque,它有着比 LinkedList(当作栈或队列使用时)有着更好的性能。
继承体系
通过继承体系,我们可以看到 LinkedList 不仅实现了 List 接口,还实现了 Queue 和 Deque 接口,所以它既能作为 List 使用,也能作为双端队列使用,当然也可以作为栈使用。
LinkedList 实现了 Cloneable 和 Serializable 接口,说明其可以被克隆,也可以被序列化。
同样的,LinkedList 被克隆的时候,和 ArrayList 一样,二者均是浅拷贝。
源码解析
属性
// 元素个数
transient int size = 0;
// 双向链表的尾结点
transient Node<E> first;
// 双向链表的头结点
transient Node<E> last;
属性很简单,定义了元素个数 size 和链表的首尾节点。
Node内部类
- 典型的双链表结构。
private static class Node<E> {
E item; // data
Node<E> next; // 前驱节点
Node<E> prev; // 后继节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
构造方法
空参
public LinkedList() {
}
传入一个Collection集合
public LinkedList(Collection<? extends E> c) {
this();
// 将c中的每一个元素构造成Node插入到链表中
addAll(c);
}
||
\/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
||
\/
/*
* 从指定的索引位置开始插入一个Collection
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 检查index是否越界,index >= 0 && index <= size
checkPositionIndex(index);
// 将c转换为数组,并获取其长度
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
/*
* pred 指定index位置的Node节点的前驱节点
* succ 存储index位置的Node节点,插入完毕后拼接链表时要使用到这个节点
*/
Node<E> pred, succ;
// index == size 说明当前插入位置就是在链表的末尾开始
if (index == size) {
// succ置为null,因为直接在链表末尾插入时插入完毕不需要拼接
succ = null;
// pred直接指向尾结点即可
pred = last;
// index != size 说明此时就是在链表中插入
} else {
// succ存index位置的元素
succ = node(index);
// pred指向index的前驱节点
pred = succ.prev;
}
// 遍历c中的每一个元素,依次进行插入。
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
// 构造节点,前驱节点就是pred
Node<E> newNode = new Node<>(pred, e, null);
// pred为null,说明链表为空,此时将first指向newNode
if (pred == null)
first = newNode;
// 非空链表,将pred.next指向newNode
else
pred.next = newNode;
// pred向后移动
pred = newNode;
}
// succ == null,即上面的if判断是 -> 当前是在链表末尾插入元素
if (succ == null) {
// 直接将last指向新的尾结点即可
last = pred;
// 非在链表末尾插入元素
} else {
/*
* 此时pred指向的是尾结点
* succ指向的是原index位置的节点
* 此时需要做的事就是将两部分链表连接起来
*/
pred.next = succ;
succ.prev = pred;
}
// 当前元素个数 + numNew
size += numNew;
// 链表修改次数+1
modCount++;
return true;
}
核心方法
添加元素
linkLast(E e)
/*
* 此方法程序员无法调用,供程序员使用的add()/addLast()方法内部调用的就是此方法。
* 作用:在链表末尾添加节点。
*/
void linkLast(E e) {
// l指向尾结点
final Node<E> l = last;
// 构造newNode,newNode的前驱指向尾结点。
final Node<E> newNode = new Node<>(l, e, null);
// 尾结点指向newNode
last = newNode;
// l == null 说明当前链表中没有元素
if (l == null)
// 将first也指向newNode
first = newNode;
// 原尾结点的next指向newNode成功添加
else
l.next = newNode;
size++;
// modCount+1 表示修改了一次链表结构
modCount++;
}
linkFirst(E e)
/*
* 此方法程序员无法调用,供程序员使用的addFirst()方法内部调用的就是此方法。
* 作用:将新添加的节点作为链表头部。
*/
private void linkFirst(E e) {
// f指向原头结点
final Node<E> f = first;
// 创建新节点,前驱设置为null
final Node<E> newNode = new Node<>(null, e, f);
// first指向newNode,即newNode是当前的头结点
first = newNode;
// f == null,说明原链表中没有节点
if (f == null)
// 此时向链表中添加一个元素后,链表中有一个元素,所以将last也指向newNode。
last = newNode;
else
// 将原头结点的前驱指向newNode。
f.prev = newNode;
size++;
// modCount+1 表示修改了一次链表结构
modCount++;
}
add() & offer()
/*
* 在链表尾部添加元素
*/
public boolean add(E e) {
// 调用的是linkLast()
linkLast(e);
return true;
}
// ------------------------------------------------------------
/*
* 在指定索引位置前插入Node。
*/
public void add(int index, E value) {
// 检查索引是否越界
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
// 将value构造成Node插入到index位置Node的前面。
linkBefore(element, node(index));
}
||
\/
/*
* @param succ,要在succ位置前面插入一个Node。
*/
void linkBefore(E e, Node<E> succ) {
// 获取succ的前驱
final Node<E> pred = succ.prev;
// 构造一个Node,前驱是pred,后继是succ
final Node<E> newNode = new Node<>(pred, e, succ);
// succ的前驱指向newNode
succ.prev = newNode;
if (pred == null)
first = newNode;
// 一般情况 pred的后继指向newNode 完成插入操作。
else
pred.next = newNode;
size++;
modCount++;
}
// ------------------------------------------------------------
/*
* 在链表尾部添加元素
*/
public boolean offer(E e) {
// 调用add()
return add(e);
}
addFirst() & push() & offerFirst()
/*
* 头部添加元素
*/
public void addFirst(E e) {
// 调用的是linkFirst()
linkFirst(e);
}
// --------------------------------------------
public void push(E e) {
// 调用addFirst()
addFirst(e);
}
// --------------------------------------------
public boolean offerFirst(E e) {
// 调用addFirst()
addFirst(e);
return true;
}
addLast() & offerLast()
/*
* 尾部添加元素
*/
public void addLast(E e) {
// 调用的是linkLast()
linkLast(e);
}
// --------------------------------------------
public boolean offerLast(E e) {
// 调用addLast()
addLast(e);
return true;
}
在中间添加元素的方法也很简单,典型的双链表在中间添加元素的方法。
添加元素的三种方式大致如下图所示:
在队列首尾添加元素很高效,时间复杂度为 O ( 1 ) O(1) O(1)。
在中间添加元素比较低效,首先要先找到插入位置的节点,再修改前后节点的指针,时间复杂度为 O ( n ) O(n) O(n)。
查看元素
peek()
/*
* 查看头结点
*/
public E peek() {
// 获取头结点
final Node<E> f = first;
return (f == null) ? null : f.item;
}
peekFirst()
/*
* 查看头结点
*/
public E peekFirst() {
// 获取头结点
final Node<E> f = first;
return (f == null) ? null : f.item;
}
peekLast()
/*
* 查看尾结点
*/
public E peekLast() {
// 获取尾结点
final Node<E> l = last;
return (l == null) ? null : l.item;
}
删除元素
unlinkFirst(Node<E> f)
/*
* 删除元素的核心方法,外部无法调用。
* 能进入这个方法的前提就是 (f为头结点 && f != null)
* 此方法作用:将头结点删除。
*/
private E unlinkFirst(Node<E> f) {
// 获取头结点的value,最后返回。
final E element = f.item;
// next是f的后继节点
final Node<E> next = f.next;
// 将头结点f的value置为null,便于GC。
f.item = null;
// 将头结点的后继置为null
f.next = null; // help GC
// 头结点指向后继
first = next;
/*
* next后继为null,说明原链表中只有一个节点,此时删除之后链表中没有节点了,所以将last指向null
*/
if (next == null)
last = null;
else
// 普通情况,直接将next的前驱置为null,即彻底断开原头结点。
next.prev = null;
size--;
modCount++;
// 最终将原头结点的value返回。
return element;
}
unlinkLast(Node<E> l)
/*
* 删除元素的核心方法,外部无法调用。
* 能进入这个方法的前提就是 (l为尾结点 && l != null)
* 此方法作用:将尾结点删除。
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
// 获取尾结点的前驱节点
final Node<E> prev = l.prev;
// 将尾结点的value和前驱节点置为null
l.item = null;
l.prev = null; // help GC
// 将last指向尾结点的前驱节点
last = prev;
// prev == null,说明删除尾结点后链表为null,此时需要将first也置为null。
if (prev == null)
first = null;
else
// 普通情况,将前驱节点的后继置为null,彻底断开尾结点
prev.next = null;
size--;
modCount++;
// 返回原尾结点的value。
return element;
}
removeFirst() & pollFirst() & pop() & poll()
/*
* 删除头结点
*/
public E removeFirst() {
final Node<E> f = first;
// 没有元素,抛出异常
if (f == null)
throw new NoSuchElementException();
// 调用unlinkFirst() 删除头结点
return unlinkFirst(f);
}
// ------------------------------------------------
public E pop() {
// 调用removeFirst()删除头结点
return removeFirst();
}
// ------------------------------------------------
public E pollFirst() {
final Node<E> f = first;
// 调用unlinkFirst() 删除头结点
return (f == null) ? null : unlinkFirst(f);
}
// ------------------------------------------------
public E poll() {
final Node<E> f = first;
// 调用unlinkFirst() 删除头结点
return (f == null) ? null : unlinkFirst(f);
}
removeLast() & pollLast()
/*
* 删除尾结点
*/
public E removeLast() {
final Node<E> l = last;
// 没有元素,抛出异常
if (l == null)
throw new NoSuchElementException();
// 调用unlinkLast() 删除尾结点
return unlinkLast(l);
}
// --------------------------------------------
public E pollLast() {
final Node<E> l = last;
// 调用unlinkLast()删除尾结点
return (l == null) ? null : unlinkLast(l);
}
remove(int index)
/*
* 删除指定索引位置的Node。
*/
public E remove(int index) {
// 检查索引
checkElementIndex(index);
// 遍历找到index位置的Node,然后将其删除。
return unlink(node(index));
}
||
\/
// --------------------------------------------
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
// 获取x节点的后继
final Node<E> next = x.next;
// 获取x节点的前驱
final Node<E> prev = x.prev;
// 如果前驱节点为空 则说明当前元素是链表的头结点 直接将first指向当前节点的后继节点
if (prev == null) {
first = next;
} else { // 前驱节点不为空
// x.prev.next = x.next 变相的删除x节点
prev.next = next;
// 断开x的前驱节点 彻底删除x
x.prev = null;
}
// 如果后继节点为空 则说明当前元素是链表的尾结点 直接将last指向当前节点的前驱节点
// 内部逻辑与上面一样
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
// 将当前元素置为空 help GC
x.item = null;
size--;
modCount++;
return element;
}
删除元素的三种方法都是典型的双链表删除元素的方法,大致流程如下图所示。
在队列首尾删除元素很高效,时间复杂度为 O ( 1 ) O(1) O(1)。
在中间删除元素比较低效,首先要找到删除位置的节点,再修改前后指针,时间复杂度为 O ( n ) O(n) O(n)。
获取元素
getFirst()
/*
* 获取头结点的value值
*/
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
getLast()
/*
* 获取尾节点的value值
*/
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
get()
/*
* 获取指定索引位置节点的value。
*/
public E get(int index) {
// 检查索引是否越界(index >= 0 && index < size)
checkElementIndex(index);
return node(index).item;
}
// -----------------------------------------------------------
/*
* 遍历链表返回指定位置的Node。
*/
Node<E> node(int index) {
// 这里判断要查找的Node是在链表的前半部分,从头节点向后找
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
// 要查找的Node在链表的后半部分,从尾结点向前找
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
常用方法
set()
/*
* 修改指定索引位置节点的value。
*/
public E set(int index, E element) {
// 检查索引是否越界
checkElementIndex(index);
// 获取index位置的Node
Node<E> x = node(index);
// 获取value
E oldVal = x.item;
// 修改value
x.item = element;
// 返回原value
return oldVal;
}
indexOf()
/*
* 从链表的头结点开始找,找到指定value的Node第一次出现的索引。
*/
public int indexOf(Object o) {
int index = 0;
if (o == null) {
// 从头开始遍历链表
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
// 不存在,返回-1
return -1;
}
contains()
/*
* 判断链表中是否存在指定的value的Node
* 底层调用的是indexOf()
*/
public boolean contains(Object o) {
// indexOf()返回-1说明链表中没有指定value的Node
return indexOf(o) >= 0;
}
size()
/*
* 返回链表中的元素个数
*/
public int size() {
return size; // 直接返回size
}
栈操作
前面我们说了,LinkedList 是双端队列,还记得双端队列可以作为栈使用吗?
public void push(E e) {
addFirst(e);
}
public E pop() {
return removeFirst();
}
栈的特性是 LIFO(Last In First Out),所以作为栈使用也很简单,添加删除元素都只操作队列首节点即可。
总结
(1)LinkedList 是一个以 双向链表
实现的 List;
(2)LinkedList 还是一个双端队列,具有 队列
、双端队列
、栈
的特性;
(3)LinkedList 在队列首尾添加、删除元素非常高效,时间复杂度为 O ( 1 ) O(1) O(1);
(4)LinkedList 在中间添加、删除元素比较低效,时间复杂度为 O ( n ) O(n) O(n);
(5)LinkedList 不支持随机访问,所以访问非队列首尾的元素比较低效;
(6)LinkedList 在功能上等于 ArrayList + ArrayDeque;
参考文章
- 彤哥读源码_死磕 java集合之LinkedList源码分析
- shstart7_LinkedList源码解析
- Java 全栈知识体系_Collection - LinkedList源码解析