书接上一篇ArrayList源码解析,这一节继续分析LinkedList在Java8中的实现,它一样实现了List接口,不过由名字就能够知道,内部实现是基于链表的,并且是双向链表,因此Linked List在执行像插入或者删除这样的操做,效率是极高的,相对地,在随机访问方面就弱了不少。 本文基于JDK1.8中LinkedList源码分析 ####类定义 java
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
复制代码
由上图以及类定义片断可知,LinkedList继承了AbstractSequentialList而且实现List,Deque,Cloneable, Serializable接口。 其中,AbstractSequentialList相较于AbstractList(ArrayList的父类)
,只支持次序访问,而不支持随机访问,由于它的 get(int index)
,set(int index, E element)
, add(int index, E element)
, remove(int index)
都是基于迭代器实现的。因此在LinkedList使用迭代器遍历更快,而ArrayList使用get (i)更快。 接口方面,LinkedList多继承了一个Deque接口,因此实现了双端队列的一系列方法。node
####基本数据结构数组
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
复制代码
LinkedList中主要定义了头节点指针,尾节点指针,以及size用于计数链表中节点个数。那么每个Node的结构如何呢?bash
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;//前驱节点
}
}
复制代码
能够看出,这是一个典型的双向链表的节点。数据结构
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
复制代码
检查非空后,直接返回first节点的item函数
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
复制代码
检查非空后,直接返回last节点的item源码分析
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
复制代码
首先检查index范围,而后调用node(index)获取index处的节点,返回该节点的item值。 看看node(index)的实现,后面不少地方借助于这个小函数:post
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { //判断index是在链表偏左侧仍是偏右侧
Node<E> x = first;
for (int i = 0; i < index; i++) //从左边往右next
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--) //从右往左prev
x = x.prev;
return x;
}
}
复制代码
由上面能够看出,在链表中找一个位置,只能经过不断遍历。ui
另外还有IndexOf,LastIndexOf操做,找出指定元素在LinkedList中的位置: 也是一个从前找,一个从后找,只分析下IndexOf操做:this
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++;
}
}
return -1;
}
复制代码
主要也是不断遍历,找到值相等的节点,返回它的Index。
####更改节点的值 主要也就是一个set函数
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
复制代码
根据index找到指定节点,更改它的值,而且会返回原有值。
####插入节点 LinkedList实现了Deque接口,支持在链表头部和尾部插入元素:
public void addFirst(E e) {
linkFirst(e);
}
public void addLast(E e) {
linkLast(e);
}
复制代码
这里咱们能够看到内部实现的函数是linkFirst
,linkLast
, 链表头插入元素:
private void linkFirst(E e) {
final Node<E> f = first; //现将原有的first节点保存在f中
final Node<E> newNode = new Node<>(null, e, f); //将新增节点包装成一个Node节点,同时该节点的next指向以前的first
first = newNode; //将新增的节点设成first
if (f == null)
last = newNode; //若是原来的first就是空,那么新增的newNode同时也是last节点
else
f.prev = newNode; //若是不是空,则原来的first节点的前置节点就是如今新增的节点
size++; //插入元素后,节点个数加1
modCount++; //还记得上一篇讲述的快速失败吗?这边依然是这个做用
}
复制代码
链表尾插入元素
void linkLast(E e) {
final Node<E> l = last; //将尾节点保存到l中
final Node<E> newNode = new Node<>(l, e, null); //把e包装成Node节点,同时把该节点的前置节点设置为l
last = newNode; //把新插的节点设置为last节点
if (l == null)
first = newNode; //若是原来的last节点为空,那么新增的节点在乎义上也是first节点
else
l.next = newNode; //不然的话还要将newNode设为原有last节点的后继节点,因此newNode如今是新的Last节点
size++;
modCount++;
}
复制代码
在指定index处插入元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
复制代码
首先调用checkPositionIndex检查index值是否在范围内,若是index在最后的话,就调用在尾部插入的函数,不然调用LinkBefore,主要看LinkBefore如何实现的:
void linkBefore(E e, Node<E> succ) { //在succ节点前插入newNode
// assert succ != null;
final Node<E> pred = succ.prev; //将succ的前置节点记为pred
final Node<E> newNode = new Node<>(pred, e, succ); 以pred为前置节点,以succ为后继节点创建newNode
succ.prev = newNode; //将new Node设为succ的前置节点
if (pred == null)
first = newNode; //若是原有的succ的前置节点为空,那么新插入的newNode就是first节点
else
pred.next = newNode; // 不然,要把newNode设为原来pred节点的后置节点
size++;
modCount++;
}
复制代码
其他几个经常使用的add方法也是基于以上函数:
public boolean add(E e) {
linkLast(e);
return true;
}
复制代码
add
函数默认在函数尾部插入元素
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
复制代码
addAll(Collection<? extends E> c)
指的是在list尾部插入一个集合,具体实现又依赖于addAll(size,c)
,指的是在指定位置插入一个集合:
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); // 检查index位置是否超出范围
Object[] a = c.toArray(); //将集合c转变成Object数组 同时计算数组长度
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) { //若是插入位置为尾部,succ则为null,原来链表的last设置为此刻的pred节点
succ = null;
pred = last;
} else {
succ = node(index); //不然,index所在节点设置为succ,succ的前置节点设为pred
pred = succ.prev;
}
for (Object o : a) { //循环遍历数组a
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null); //以a为element元素构造Node节点
if (pred == null)
first = newNode; //若是pred为空,此Node就为first节点
else
pred.next = newNode; //不然就往pred后插入该Node
pred = newNode; //newNode此刻成为新的pred, 这样不断循环遍历,把这个数组插入到链表中
}
if (succ == null) { //若是succ为空,就把插入的最后一个节点设为last
last = pred;
} else {
pred.next = succ; //不然,把以前保存的succ接到pred后面
succ.prev = pred; //而且把succ的前向指针指向插入的最后一个元素
}
size += numNew; //记录增加的尺寸
modCount++; //记录修改次数
return true;
}
复制代码
具体流程能够看代码中的注释。
####删除节点#### 由于实现了Deque
的接口,因此仍是实现了removeFirst
, removeLast
方法。
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
复制代码
首先保存fisrt节点到f,若是为空,抛出NoSuchElementException
异常,实际仍是调用unlinkFirst
完成操做。
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item; //把f.item的值保存到element
final Node<E> next = f.next; //把f.next的值记住
f.item = null;
f.next = null; // help GC //把item和next的都指向null
first = next; //next成为实际的first节点
if (next == null) //next为空的话,由于next是第一个节点,因此链表都是空的,last也为空
last = null;
else
next.prev = null; //next不为空,也要将它的前驱节点记为null,由于next是第一个节点
size--; //节点减小一个
modCount++; //操做次数加1
return element;
}
复制代码
removeLast的实现基本和removeFirst对称:
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
复制代码
主要实现仍是借助于unlinkLast
:
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item; //保存最后一个节点的值
final Node<E> prev = l.prev; //把最后一个节点的前驱节点记为prev,它将成为last节点
l.item = null;
l.prev = null; // help GC //l节点的item和prev都记为空
last = prev; //此时设置刚才记录的前驱节点为last
if (prev == null) //prev为空的话,说明要删除的l前面原来没节点,那么删了l,整个链表为空
first = null;
else
prev.next = null; //prev成为最后一个节点,没有后继节点
size--;
modCount++;
return element;
}
复制代码
在指定index删除
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
复制代码
首先也是检查index是否合法,不然抛出IndexOutOfBoundsException
异常。 若是删除成功,返回删除的节点。 具体实现依赖于unlink
,也就是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;
} 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;
}
复制代码
还有种删除是以删除的元素做为参数:
public boolean remove(Object o) {
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;
}
复制代码
####清空列表
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
复制代码
由代码看出,也就是循环遍历整个链表,将每一个节点的每一个属性都置为空。
LinkedList还定义了不少别的方法,基本上和上面分析的几个函数功能相似
####ListIterator 最后重点说一下LinkedList中如何实现了ListIterator迭代器。 ListIterator是一个更增强大的Iterator迭代器的子类型,它只能用于各类List类的访问。尽管Iterator只能向前移动,可是ListIterator能够双向移动。它还能够产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,能够用set()
方法替换它访问过得最后一个元素。
定义以下:
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
复制代码
指定index,能够在一开始就获取一个指向index位置元素的迭代器。 实际上LinkedList是实现了ListItr类:
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next; //用于记录当前节点
private int nextIndex; //用于记录当前节点所在索引
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index); //返回index处的节点,记录为next
nextIndex = index; //记录当前索引
}
public boolean hasNext() {
return nextIndex < size; //经过判断nextIndex是否还在size范围内
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next; //记录上一次的值
next = next.next; //日后移动一个节点
nextIndex++; //索引值也加1
return lastReturned.item; //next会返回上一次的值
}
public boolean hasPrevious() { //经过哦按段nextIndex是否还大于0,若是<=0,就证实没有前驱节点了
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev; //往前移动
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
复制代码
咱们能够发如今ListIterator的操做中仍然有checkForComodification
函数,并且在上面叙述的各类操做中仍是会记录modCount,因此LinkedList也是会产生快速失败事件的。