链表是对上一篇博文所说的顺序表的一种实现。java
与ArrayList思路大相径庭,链表的实现思路是:node
从链表的概念来说,它能够算是一种递归的数据结构,由于链表拿掉第一个元素剩下的部分,依然构成一个链表。数据结构
<!-- more -->多线程
插入元素。实际上插入元素须要看状况:函数
首先继承结构和ArrayList相似,实现了List接口。
可是,它继承的是继承了AbstractList类的AbstractSequentialList类,
这个类的做用也是给List中的部分函数提供默认实现,只是这个类对链表这种List的实现提供了更多贴合的默认函数实现。源码分析
还有能够注意到,LinkedList实现了Deque接口,这也很显然,链表这种结构自然就适合当作双端队列使用。优化
先来看链表的节点定义:this
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; } }
能够看到,链表节点除了保存数据外,还须要保存指向先后节点的指针。
这里,链表即有后继指针也有前驱指针,所以这是一个双向链表。spa
一组节点之间按顺序用指针指起来,就造成了链表的链状结构。线程
transient int size = 0; transient Node<E> first; transient Node<E> last;
三个属性,first和last分别指向链条的首节点和尾节点。
这样有个好处,就是链表便可以使用头插法也能够采用尾插法。
size属性跟踪了链表的元素个数。虽说遍历一遍链表也能统计到元素个数,
可是那是O(n)的费时操做。
所以,咱们能够发现链表的size方法是O(1)的时间复杂度。
public LinkedList() { }
LinkedList的代码很简单,构造函数空空如也。
空表中,first和last字段都为null。
public E get(int index) { checkElementIndex(index); return node(index).item; } public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; } 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; } }
get和set的思路都是先根据索引定位到链表节点,而后得到或设置节点中的数据,这抽象出了node函数,根据索引找到链表节点。
node
的思路也很显然,遍历一遍便可获得。
这里作了一点优化,咱们能够发现LinkedList的实现是一个双向链表,而且LinkedList持有了头尾指针。
那么,根据索引和size就能够知道该节点是在链表的前半部分仍是后半部分,
从而决定从头节点开始遍历仍是从尾节点开始遍历,这样最多遍历 N / 2次便可找到。
public boolean add(E e) { linkLast(e); return true; } public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } 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++; } void linkBefore(E e, Node<E> succ) { final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
添加/删除的思路都相似,删除的代码就不贴了。若是可以提供须要被操做的节点,就能直接移动下指针,O(1)完成。不然就须要遍历找到这个节点再操做。
须要关注两点:
以前阅读ArrayList的代码时发现了modCount这一字段,它是定义在AbstractList类中的。以前不知道它起到什么做用,此次给弄明白了。
考虑如下这段代码:
List<Integer> list = new LinkedList<>(); Iterator<Integer> it = list.listIterator(); list.add(1); it.next();
在迭代器建立以后,对表进行了修改。这时候若是操做迭代器,则会获得异常java.util.ConcurrentModificationException
。
这样设计是由于,迭代器表明表中某个元素的位置,内部会存储某些可以表明该位置的信息。当表发生改变时,该信息的含义可能会发生变化,这时操做迭代器就可能会形成不可预料的事情。
所以,果断抛异常阻止,是最好的方法。
实际上,这种迭代器迭代过程当中表结构发生改变的状况,更常常发生在多线程的环境中。
这种机制的实现就须要记录表被修改,那么思路是使用状态字段modCount
。
每当会修改表的操做执行时,都将此字段加1。使用者只须要先后对比该字段就知道中间这段时间表是否被修改。
如linkedList中的头插和尾插函数,就将modCount字段自增:
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++; } 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++; }
迭代器使用该字段来判断,
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); nextIndex = index; } public boolean hasNext() { return nextIndex < size; } public E next() { checkForComodification(); if (!hasNext()) throw new NoSuchElementException(); lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; } /* ... */ public void set(E e) { if (lastReturned == null) throw new IllegalStateException(); checkForComodification(); lastReturned.item = e; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
迭代器开始时记录下初始的值:
private int expectedModCount = modCount;
而后与如今的值对比判断是否被修改:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
这是一个内部类,隐式持有LinkedList的引用,可以直接访问到LinkedList中的modCount字段。