LinkedList源码
- 我们知道,ArrayList的底层使用一个Objectp[]数组实现的,特点是末尾添加元素快,根据下表返回数据块,缺点是再指定位置新增元素或者删除元素,需要移动位置后的所有元素.且数组需要连续内存空间,会产生碎片.
底层除了数组外,还能有其他实现吗? 答案是有的,那就是底层以链表实现集合功能.
LinkedList
- 在节点前后插入删除数据快,因为只需要修改节点的前驱和后继的引用指针即可。
- 根据下标返回数据慢,因为链表不具备RandomAccess特性,只能通过遍历返回指定下标数据
- 可以当做先进先出的队列使用,可以当做后进先出的堆栈使用
- 允许插入null元素
- 非线程安全
LinkedList除了实现了List接口外, 还实现了Deque接口, Deque是双端队列,即双端链表. 可以当做队列和堆栈使用。
链表的特点是节点通过一个前驱和后继的引用将分散在内存中的对象连接起来,就像通过一根绳把多个珠子串起来。
LinkedList有较为庞大的方法数量,因为除了实现List接口外,还实现了Deque接口并继承AbstractSequentialList抽象类。
链表的重点和难点在于节点中指向前驱和后继的两个类似指针的引用,因为无论增删改查,都会对这两个引用修改。
1、接口
1.1、Queue
队列接口,队列有先进先出(FIFO)队列,后进先出(LIFO)队列,优先级队列等。
1.2、Deque
双端队列接口,支持在两端插入和移除元素
提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。插入操作的后一种形式是专为使用有容量限制的 Deque 实现设计的
1.3、AbstractSequentialList
继承了AbstractList,并给出了一个骨架实现,
增删改查依赖于一个获取迭代器的方法。
public abstract ListIterator<E> listIterator(int index);
方法的实现在LinkedList中,返回一个链表迭代器。
1.4 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;
}
}
节点持有前驱和后继连个引用指针,使得可以在操作节点前后的数据都非常方便。
1.5、ListItr 迭代器
LinkedList实现了ListIterator迭代器,迭代器通过下标,从头结点开始循环获取指定下标的节点信息生成迭代器。
private class ListItr implements ListIterator<E> {
// 上一次返回的节点,remove方法就是对此节点进行删除
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
lastReturned = next;
// 节点指针元素后移
next = next.next;
// 节点指针索引后移
nextIndex++;
// 返回上一个节点信息
return lastReturned.item;
}
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
// 再next方法中,lastReturned和next是指向了两个不同的节点,而previous则将lastReturned和next指向了同一个节点
// 个人感觉这里略坑,感觉执行完这段后加上next = next.next;妥当一些,因为next如果理解为将要返回的节点,执行previous方法后lastReturned和next指向了同一节点,如果紧接着再执行next方法,又会得到一次此值
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();
// 本来我还在想这里为什么用lastReturned.next不用next,然后想起来previous方法执行后使得lastReturned和next指向了同一个节点,
Node<E> lastNext = lastReturned.next;
// 在链表中删除lastReturned节点
unlink(lastReturned);
// 调整next节点,因为lastReturned已被删除,而previous方法会使lastReturned和next指向了同一节点,所以越看越觉得previous略坑
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;
// next为空说明迭代器已返回最后一个元素,所以需要在末尾追加元素
if (next == null)
linkLast(e);
else
// 再next元素之前插入元素
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
// 循环迭代集合元素,这里会移动lastReturned和next节点指针,意味着此方法只能执行一次,第二次不会返回数据
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();
}
}
LinkedList大部分的操作都是对头结点和尾结点的操作,如果涉及下标的操作,要从头结点开始循环,取到指定节点返回数据。
Node<E> node(int index) {
// assert isElementIndex(index);
// 如果下标小于size的一般,从头结点开始循环去数据,否则从尾结点取数据
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;
}
}