前言:面试时被问到 LinkedList 的底层实现,瞬间哑口无言,所以决定狠狠地啃一下 Java源码,目标就是吊打面试官!
好了,废话不多说,进入正题。
简单介绍
很多人听到 LinkedList,应该就能猜到它的底层是用链表实现的,更准确来说,是用 双向链表 实现的,和常用的 ArrayList 不同,LinkedList 更适用于插入和删除操作较多的场景,而且LinkedList 实现了 Queue 接口,所以经常被当做队列来使用。
那么 LinkedList 底层到底是怎么实现的呢?
LinkedList 的成员变量
俗话说,学习一个类,就要先从这个类包含的属性入手,那么我们就来看看吧!
LinkedList 的主要变量:
transient int size = 0; // 双向链表的节点数量
transient Node<E> first; // 双向链表的第一个节点
transient Node<E> 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 的主要操作是怎么实现的?
我们不妨来大胆猜想一下:
add(E e)
:将指定元素添加到链表末尾。让链表的最后一个节点的 next 指向新增节点 e,让新增节点的 prev 指向最后一个节点,最后将 last 指向新增节点,size+1。get(int index)
:返回链表中指定位置的元素。检查下标是否越界,如果越界,抛异常;没越界,则从头或者从尾部开始遍历(指定节点离哪个近就从哪开始),找到指定节点后返回节点值。peek()
:检索但不删除此链表的第一个元素。若 first 为空,返回 null,否则返回 first 节点值。poll()
:检索并删除此链表的第一个元素。若 first 为空,返回 null,否则返回 first 节点值,并删除 frist 节点。remove(Object o)
:从链表中删除第一次出现的指定元素。若 o 为空,遍历链表找到值为 null 的节点并删除;否则,遍历链表找到值等于 o 的节点值 的节点,并删除。size()
:返回链表的元素数量。直接返回 size。
--------------------------------------------- 想狂虐面试官?请往下分析源码 ---------------------------------------------
LinkedList 的主要方法
接下来将分析 LinkedList 几个主要方法的源码,不要紧张,可以先放松一下,深吸一口气,其实阅读源码并没有那么难,只要掌握正确的方法和节奏,看我来简单地分析给你听:
1.add 方法
有多种不同的add()方法,我们这里分析一下比较常用的 add(E e):
注:代码中的中文注释是笔者添加的
public boolean add(E e) {
// 将 e 添加到链表尾部
linkLast(e);
return true;
}
void linkLast(E e) {
// 尾节点
final Node<E> l = last;
// 新增节点初始化,prev 指向 尾节点
final Node<E> newNode = new Node<>(l, e, null);
// last 指向 新增节点
last = newNode;
// 旧链表尾节点 为空
if (l == null)
first = newNode;
// 不为空
else
l.next = newNode;
// 链表节点数量 + 1
size++;
modCount++;
}
总结:
调用 add(E e) 方法,先初始化该新增节点,并将 prev 指向旧链表的最后一个节点,若旧链表最后一个节点为空(即链表为空),first 和 last 都指向新增节点,最后让 size +1,链表操作次数 modCount+1。
2.get 方法
LinkedList 中的 get 方法为 get(int index) ,那我们就来看一下源码:
public E get(int index) {
// 检查下标是否越界,越界则抛异常
checkElementIndex(index);
// 获取指定下标节点的节点值
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
Node<E> node(int index) {
// 下标离头节点比较近, 从头节点开始遍历
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;
}
}
总结:
调用 get(int index) 方法,先判断下标 index 是否越界,越界则抛异常;没越界则根据下标选择从头节点或者从尾节点开始遍历,找到指定下标节点,则返回该节点的节点值。
3.poll 方法
peek() 方法和 poll() 方法都是检索链表的头节点,但是 poll() 检索后要删除该节点,比较复杂,所以我们只分析 poll()。
public E poll() {
// 头节点为空,返回 null, 否则删除头节点并返回
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
// 删除头节点并返回
private E unlinkFirst(Node<E> f) {
// 获取头节点元素值
final E element = f.item;
// 获取第二个节点
final Node<E> next = f.next;
// 头节点元素值 置空
f.item = null;
// 头节点 next 置空, 后期 JVM 会进行 GC 回收
f.next = null; // help GC
// first 指向 第二个节点
first = next;
// 第二个节点为空
if (next == null)
last = null;
// 第二个节点不为空
else
next.prev = null;
// 链表节点数量 + 1
size--;
modCount++;
return element;
}
总结:
调用 poll() 方法,先判断头节点是否为空,为空则返回 null;不为空则将 first 指向 第二个节点后,返回头节点的值,并删除头节点。
4.remove 方法
LinkedList 的 remove 方法也有很多个,我们来分析常用的 remove(Object o) 方法(删除第一次出现指定元素的节点)。
public boolean remove(Object o) {
// 若 o 为空
if (o == null) {
// 遍历找到值为 null 的节点并删除
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
// 若 o 不为空
} else {
// 遍历找到值为 x的值 的节点并删除
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
//删除 x 节点
E unlink(Node<E> x) {
// assert x != null;
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;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
总结:
调用 remove(Object o),若 o 为空,则遍历链表找到值为 null 的节点并删除;若 o 不为空,则遍历链表找到值为 o的值 的节点并删除。
分析结束啦!谢谢观看~