今天我们来剖析LinkedList源码是什么样的?源码jdk采用jdk1.8,在看源码之前我想大家一定了解了单向链表和双向链表的表结构以及如何增加删除节点,看源码也就快一些,不过不要紧,自己拿一张纸跟着画一遍也就清楚了。(代码解析步骤注释中有)
打开源码
首先我们得知道:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList是继承自AbstractSequentialList这个抽象类,并且实现了List接口,上一篇的ArrayList也是实现了List接口,因为他们都是List嘛,所以这节可以与上篇ArrayList源码学习对比学习。
接下来我们分为三个部分来讲:LinkedList的主要属性、主要方法、以及内部的private辅助方法
LinkedList的主要属性
//Node 节点,LinkedList内部是一个个的Node节点
transient int size = 0;//LinkedList的大小长读,也就是节点个数
transient Node<E> first;//LinkedList中首节点
transient Node<E> last;//LinkedList中尾节点
LinkedList本身就是一个双向链表,所以下面内容也会出现前驱与后继节点
LinkedList主要方法
public LinkedList() {
}
//传入一个集合类
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
其中有两个构造函数,一个无参数构造函数,另外一个有集合参数的构造函数,无参数构造函数也未指定LinkedList中链表的大小,我们不妨想一想,链表不像数组需要初始化大小,链表可以随时添加元素。在执行有参数构造方法之前,先调用了无参数构造函数。
我们进入到addAll()方法中:
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
结果又调用了addAll(size,c)方法,而size是LinkedList的大小,也就是将集合直接添加在链表尾部:
public boolean addAll(int index, Collection<? extends E> c) {
//检查插入节点是否合法
checkPositionIndex(index);
//把集合元素存到a数组中
Object[] a = c.toArray();
//拿到数组长度
int numNew = a.length;
//传入的是空集合,返回
if (numNew == 0)
return false;
//succ是待插入的节点,pred是待插入节点的前一个节点。也就是你最后插的节点位置是在succ之前。
Node<E> pred, succ;
//如果插入节点的位置是原链表大小,那么就将集合元素节点插入到原链表末尾
if (index == size) {
succ = null;//succ也就是null
pred = last;//succ的前节点也就是原链表最后一个节点
} else {//如果插入的位置是链表中或者链表头
//node()函数计算出插入的节点位置
succ = node(index);
//succ的前驱节点就是pred
pred = succ.prev;
}
//找到了要插入的位置,就开始循环插入a数组的中元素
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//把a数组中的元素包装为节点
Node<E> newNode = new Node<>(pred, e, null);
//如果前驱节点为空,那么原链表为空,则新建插入的节点为首节点。
if (pred == null)
first = newNode;
else
//一个一个插入节点
pred.next = newNode;
//并挪动pred的位置,使其是待插入节点的前驱节点
pred = newNode;
}
//此时,链表的插入已经完成
//待插入的节点为空,就证明你是插入到了原链表尾部,让last指向即可
if (succ == null) {
last = pred;
} else {
//将插入的最后位置前驱与后继相连
pred.next = succ;
succ.prev = pred;
}
//修size大大小
size += numNew;
modCount++;
return true;
}
如果添加过程不是很清楚,跟着思路画一遍也就清楚了。
辅助方法
找到指定位置的节点并返回
Node<E> node(int index) {
// assert isElementIndex(index);
//如果寻找节点的位置在链表前1/2出,则从前往后查找
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
//如果寻找节点的位置在链表后1/2出,则从后往前查找
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
public void addFirst(E e) {
linkFirst(e);
}
addFirst添加节点至首位置
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
//如果原链表为空,新建节点就是第一个节点
if (f == null)
last = newNode;
else
//放到第一个位置,让原首节点的前驱指向新节点
f.prev = newNode;
//链表大小自增
size++;
modCount++;
}
linkLast添加元素至链表尾部
void linkLast(E e) {
//拿到最后一个节点
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//链表为空,新节点为首节点
if (l == null)
first = newNode;
else
//新节点指向最后一个节点last
l.next = newNode;
//链表大小自增
size++;
modCount++;
}
在某个index位置添加节点
public void add(int index, E element) {
checkPositionIndex(index);
//插入末尾
if (index == size)
linkLast(element);
else
//插入到指定位置
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//找到待插入节点的前驱
final Node<E> pred = succ.prev;
//创建节点
final Node<E> newNode = new Node<>(pred, e, succ);
//待插入节点的前驱节点是newNode
succ.prev = newNode;
//前驱为空,则原链表为空,新节点为首节点
if (pred == null)
first = newNode;
else
//待插入节点的后继是新节点
pred.next = newNode;
//链表大小自增
size++;
modCount++;
}
LinkedList提供了两种迭代器,一种是返回Iterator,另一种返回ListIterator
①返回ListIterator迭代器:
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
②返回Iterator迭代器:
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
LinkedList的迭代器也是提供了两种,一种是指提供向后遍历的Iterator,另一种是List的专有迭代器ListIterator。
小结:
LinkedList底层是一个双向链表,其内部是一个个Node节点,非同步,其允许为null值
由于LinkedList的底层是双向链表,因此其顺序访问的效率非常高,而随机访问的效率就比较低了,因为通过索引去访问的时候,首先会比较索引值和链表长度的1/2,若前者大,则从链表尾开始寻找,否则从链表头开始寻找,这样就把双向链表与索引值联系起来了。