引言
上一篇文章咱们一起分析了ArrayList,本篇咱们一起来看看它的姐妹LinkedList,分析的源码是JDK8版本。
1、LinkedList结构图
LinkedList继承了AbstractSequentialList,实现了List、Deque、Cloneable、Serializable接口,如下图所示:
- AbstractSequentialList类:继承AbstractList,从该抽象类的英文注释中可以知道,该类提供了List接口的骨干实现,对“顺序访问”数据存储(如链接列表)支持。对于随机访问数据(如数组),应该优先使用 AbstractList。
- Deque 接口:Deque定义了一个线性Collection,支持在两端插入和删除元素。
现在,我们应该知道了LinkedList是一个双向链表结构,支持复制、序列化的。
2、分析源码
我们分析的顺序还是从对象的属性,构造方法,常用方法进行分析
2.1、属性
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
//...省略部分代码
}
上面源码中为LinkedList中的基本属性,其中size为LinkedList的长度,first为指向头结点,last指向尾结点,Node为LinkedList的一个私有内部类,其定义如下,即定义了item(元素),next(指向后一个元素的指针),prev(指向前一个元素的指针)。
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中的元素为[“A”,”B”,”C”],其内部的结构如下图所示:
从上图,我们可以清晰的看出LinkedList底层是双向链表的实现。
2.2、构造方法
public LinkedList() {}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
源码中只有两个构造方法,一个是空构造方法,啥事也没做,另外一个构造方法可以添加集合元素,转化为链表,我们可以看到方法是addAll,我们先看添加单个元素的add方法,addAll方法与其大同小异。
2.3、常用方法
2.3.1、新增
public boolean add(E e) {
linkLast(e);
return true;
}
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
l.next = newNode;
size++;
modCount++;
}
其实通过源码可以看出添加的过程如下
1.记录当前末尾节点,构造另外一个指向末尾节点的指针l
2.产生新的节点:在链表的末尾添加,next是为null的
3.last指向新的节点
4.这里有个判断,判断是否为第一个元素(当l==null时,表示链表中是没有节点的), 如果是第一节点,则使用first指向这个节点,若不是则当前节点的next指向新增的节点
5.size增加,操作记录modCount增加
2.3.2、删除
//方法1.删除指定索引上的节点
public E remove(int index) {
//检查索引是否正确
checkElementIndex(index);
//这里分为两步,第一通过索引定位到节点,第二删除节点
return unlink(node(index));
}
//方法2.删除指定值的节点
public boolean remove(Object o) {
//判断是否为null,然后进行遍历删除
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
通过源码可以看出两个方法都是通过unlink()删除,咱们先看方法一中定位到节点的node(index)方法是如何实现的:
Node<E> node(int index) {
// assert isElementIndex(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;
}
}
- 先通过二分法,确定index的位置,是靠近first还是靠近last
- 若靠近first则从头开始查询,否则从尾部开始查询,可以看出这样避免极端情况的发生,也更好的利用了LinkedList双向链表的特征
下面再分析删除节点中最核心的方法unlink()
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;
/删除的是第一个节点,first向后移动
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//删除的是最后一个节点,last向前移
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
- 1.获取到需要删除元素当前的值,指向它前一个节点的引用,以及指向它后一个节点的引用。
- 2.判断删除的是否为第一个节点,若是则first向后移动,若不是则将当前节点的前一个节点next指向当前节点的后一个节点
- 3.判断删除的是否为最后一个节点,若是则last向前移动,若不是则将当前节点的后一个节点的prev指向当前节点的前一个节点
- 4.将当前节点的值置为null
- 5.size减少并返回删除节点的值
上面就是LinkedList添加、删除元素的内部实现。
2.4、总结
上一篇我们分析了ArrayList,下面我们来总结对比下ArrayList和LinkedList的区别:
2.4.1、相同点
- 1.接口实现:都实现了List接口,都是线性列表的实现
- 2.线程安全:都是线程不安全的
2.4.2、区别
- 1.底层实现:ArrayList内部是数组实现,而LinkedList内部实现是双向链表结构
- 2.接口实现:ArrayList实现了RandomAccess可以支持随机元素访问,而LinkedList实现了Deque可以当做队列使用
- 3.性能:新增、删除元素时ArrayList需要使用到拷贝原数组,而LinkedList只需移动指针,查找元素 ArrayList支持随机元素访问,而LinkedList只能一个节点接一个节点的去遍历
结束语
写源码分析文章是需要耗费巨大的精力的,如果本篇文章对你有帮助,请随手点个赞,谢谢!