概述
上一篇我们介绍了ArrayList,我们知道它的底层是基于数组实现的,提到数组,我们就马上会想到它的兄弟链表,今天我们要介绍的LinkedList就是基于链表实现的。
继承结构
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
List接口:列表,add、set、等一些对列表进行操作的方法
Deque接口:有队列的各种特性,可以当队列用
Cloneable接口:能够复制,使用那个copy方法。
Serializable接口:能够序列化。
注意:并没有实现RandomAccess:那么就推荐使用iterator,在其中就有一个foreach,增强的for循环,其中原理也就是iterator,我们在使用的时候,使用foreach或者iterator都可以。
源码分析
先看有哪些成员变量
//元素个数
transient int size = 0;
//首节点
transient Node<E> first;
//尾节点
transient Node<E> last;
在这里,我们发现first和last都是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是底层是一个双向链表,但究竟是不是双向循环链表呢?我们还得继续往下看。
有两个构造方法
//空实现
public LinkedList() {
}
//用一个集合构建
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
第二个构造方法中,出现了addAll(e)方法,显然该方法实现了把c中所以元素加到一个空链表中,我们来看看它的具体实现
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
只有短短两行,主要调用了我们的addAll(int, E)方法,继续看它的实现
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); //检查边界
Object[] a = c.toArray(); // 把c里的值转化为数组
int numNew = a.length;
if (numNew == 0) //如果c中无元素,则增加失败
return false;
Node<E> pred, succ; // 两个指针,一个指前,一个后
if (index == size) { //注意我们构造函数中一开始传进来的就是size,所以会进入
succ = null;
pred = last;
} else { //当我们传入的不是size的时候,进入这里
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null); //注意,创建该节点的时候新节点已经和以前以后连上了,但只是单连接
if (pred == null)
first = newNode; //如果没有前一个节点,则把该节点置为首节点
else
pred.next = newNode; // 新节点前面的节点也连上了它,实现了双向连接
pred = newNode; //指针移动
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
代码有点长,但是不难,容易看懂,同时在这个方法中,我们也可以知道底层只是个双向链表,并非是双向循环链表啦,事实上JDK1.7之前一直是循环链表来着,至于为啥要改,我也不敢妄下定论….
好啦,分析完了构造函数,我们接着看他一些常用的方法的实现把
常用方法
1.add(E)
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++;
}
哈哈,代码不难,一看就懂
2.add(int , e) 在指定位置增加元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size) //如果刚好是在最后一个增加,就直接加啦
linkLast(element);
else
linkBefore(element, node(index)); //在中间加
}
//在中间加的时候利用一分为二思想,看index离头近还是离尾近
Node<E> node(int index) {
if (index < (size >> 1)) { //index<size/2 如果离头近,则从前往后找
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;
}
}
具体的思路我都写在注释上啦,在node(index)的时候,运用了一分为二思想,结合了双向链表的优点,有点巧妙
3.remove(Object) 删除第一次的Object
其实思路不难,就是先找到出现的位置,然后执行删除操作
public boolean remove(Object o) {
if (o == null) { // 区分是否为空,因为空值无法执行 equals方法
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了
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就行啦
first = next;
} else { //前继指向它的后继,相当于跳过它啦
prev.next = next;
x.prev = null; //置空
}
if (next == null) {
last = prev; // 太简单,跳过
} else {
next.prev = prev; //next的前继指向 原节点的前继。
x.next = null;
}
x.item = null; //置空,让GC回收它
size--;
modCount++;
return element;
}
可以结合上面的图来理解
4.remove(index) 删除给定位置的对象啦
其实原理都差不多,首先是定位,再删除…注意删除的点是头或者尾节点这种特殊情况就行啦
5.get(index)
public E get(int index) {
checkElementIndex(index); //检查边界
return node(index).item;
}
核心定位代码node(index)之前已经介绍过啦,虽然进行了一些优化,性能已经变为了O(n/2),但还是非常低效的。
总结
- LinkedList 插入,删除都是移动指针效率很高。
- 查找需要进行遍历查询,效率较低
与ArrayList的比较
- ArrayList 基于动态数组实现,LinkedList 基于双向链表实现;
- ArrayList 支持随机访问,LinkedList 不支持;
- LinkedList 在任意位置添加删除元素更快。
想说的话
哈哈第二次看源码啦,虽然速度慢,但感觉还是不错的啦,分析源码的思路感觉更加顺畅了些。有些观点参考了其他的博客,在浏览其他博客的时候,也有发现其他博客说的不够准确的地方,也正是这样,让我感觉博客必须要严谨些,一定要尽量多了解些,不然对其他初学者产生勿扰就糟糕啦哈哈哈哈