本解析源码来自JDK1.7
LinkedList许多方法是为了适配其实现的接口,本质上都是双向链表的操作
LinkedList概要
- 基于双向链表,主要实现了List和Deque接口,Deque接口继承自Queue,所以LinkedList同时实现了Queue接口
- 由于其基于双向链表,操作需要操作连接指针数数较多,所以线性操作系数比ArrayList较大
- 插入删除快,随机访问慢
- 线程不安全,修改列表结构的操作需要外部同步,更新列表元素值不算结构修改
// 外部同步方法
List list = Collections.synchronizedList(new LinkedList(...));
- 允许null值
- 随机访问index位置元素,需要遍历列表,从头部和尾部选择接近的一端开始遍历
- 迭代器通过比较修改次数(modCount)实现fail-fast机制,由于线程不安全,无法保证modCount的值的可靠性,所以fail-fast可能出现非预期结果
实现接口
- Cloneable 实现浅拷贝
- Serializable 可序列化
- List 可顺序访问
- Deque 实现双端队列接口
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
接口方法
由于LinkedList同时实现了List和Deque接口,所以要实现较多的方法,但是都是基于双向链表的一些方法。
- List
public interface List<E> extends Collection<E> {
// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
// Modification Operations
boolean add(E e);
boolean remove(Object o);
// Bulk Modification Operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
// Comparison and hashing
boolean equals(Object o);
int hashCode();
// Positional Access Operations
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
// Search Operations
int indexOf(Object o);
int lastIndexOf(Object o);
// List Iterators
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
// View
List<E> subList(int fromIndex, int toIndex);
}
- Queue
注意对于Queue接口来说,方法可以分为两组 - 抛出异常的方法 add(入队) remove(出队) element(查看对头)
- 不抛出异常的方法 offer poll peek
两组对应的方法功能基本相同,只是在处理队列为空时remove和element会抛出异常NoSuchElementException,poll和peek会返回null而不是抛出异常,在队列满时,add会抛出IllegalArgumentException,offer则不会抛出异常。
public interface Queue<E> extends Collection<E> {
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
}
- Deque extends Queue
Deque不仅定义FIFO(first-in-first-out)的Queue方法,而且定义了LIFO(last-in-first-out)的stack方法
其方法可以分为以下几类 - 抛出异常的FIFO方法 addFirst,addLast,removeFirst,removeLast,getFirst,getLast,add,remove,element
- 不抛出异常的FIFO方法 offerfirst,offerLast,pollFirst,pollLast,peekFirst,peekLast,offer,poll,peek
- 栈方法 LIFO方法 push,pop,peek
- 迭代器 正向Iterator 反向 descendingIterator
public interface Deque<E> extends Queue<E> {
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
void push(E e);
E pop();
boolean remove(Object o);
boolean contains(Object o);
public int size();
Iterator<E> iterator();
Iterator<E> descendingIterator();
}
主要成员
双向链表主要成员就是其头尾节点
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
基本节点
双向链表基本节点
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;
}
}
构造方法
- 构造空的双向链表
- 基于Collection构造链表,调用addAll方法将新的元素插入到链表末尾
- addAll方法实现
- 首先检查位置的合法性(0=<\pos<=size)包含开始和结尾
- 检查要插入的元素的个数,为0直接返回
- 找到要插入的元素的前驱和后继,如果在结尾插入,那么后继为null,前驱为last,否则后继为node(index)(在index位置前插入,index==lenght则在最后一个元素之后),前驱为succ.prev
- node(index) 返回位置index的元素,首先要判断index距离first近还是last近
- 根据找到的前驱进行元素插入,当前驱为空,说明first为null,更新此节点为first,否则连接到pred之后,更新pred
- 将插入的末尾元素与succ连接起来,如果succ==null说明是在尾部插入,pred更新为新的last,否则将pred与succ连接起来
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
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;
}
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;
}
}
位置检查
有两种位置,一是元素位置,一是插入位置,区别在于能不能等于size,插入在index之前,当index==size时插入整个链表之后
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
插入节点
新建节点时只是连接了该节点到其他节点的指针,注意还需要连接其他节点到该节点的指针
- 插入头结点 如果仅有一个节点那么同时也是尾节点
- 插入尾节点 如果仅有一个节点那么同时也是头节点
- 插入普通节点 在给定的节点之前插入,(默认当前节点不为空),找到前驱,新建节点,连接左右节点,注意插入节点可能是头结点,但是不可能是尾
两个方法的可见性为什么不一样?
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++;
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
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++;
}
删除节点
- 删除头结点,只需要first=first.next,然后把first的prev赋值null,考虑到可能只有一个节点,此时尾节点也会为null,否则头结点前驱置为null,f.item = null和f.next=null句没有程序逻辑依然正确,但是由于该节点还有指向其他节点的引用,所以不会被垃圾回收,置为null可以帮助垃圾回收器回收
- 删除尾节点,类比删除头结点
- 删除当前节点,prev.next = next next.prev = prev,考虑到可能是头结点,或尾节点需要更新头尾节点,其中x.item=null,x.prev = null,x.next=null帮助垃圾回收器确定回收
- 清空链表 逻辑上只需要将first,last置为null即可,但是清空节点之间的连接可以帮助分代垃圾回收器回收存活多代的垃圾,即便还有迭代器指向这些节点,依然后释放内存
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
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 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++;
}
查找节点
- 按值查找,从头到尾遍历整个链表,注意由于列表可能存在null值,查找null需要单独处理,而不能用x.equals(null)来处理
- 按位置查找,首先判断位置距离头结点近还是尾节点近
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;
}
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;
}
}
迭代器
与ArrayList类似
- LinkedList实现了两种迭代器,其中ListIterator除了实现基本的Iterator方法(hasNext,Next,remove),还包含更丰富的方法
- 迭代器作为LinkedList的内部类,可以直接访问修改ArrayList
- ListIterator额外实现的方法
- listIterator(index) 可以指定开始遍历的位置
- hasPrevious 有没有前驱
- previous 返回前驱
- add 实现添加元素
- set 更新上次访问的元素
- previousIndex() nextIndex() 返回下一个,和上一个位置
NOTE
remove,add,set方法都是改变上次被访问元素位置进行操作,连续调用两次以上就会出现问题
序列化
与ArrayList类似,同样代表列表元素的first,last,size不会被默认序列化函数序列化,列表元素通过逐个顺序写入,顺序读出的方式进行序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
fail-fast机制
与ArrayList类似同样有fail-fast机制
- 源码中凡是修改List结构(插入,删除,打乱顺序,调整容量,不包含set更新元素),都会涉及到modCount++
- 在LinkedList类创建迭代器之后,除非通过迭代器自身remove或add对列表结构进行修改,否则在其他线程中以任何形式对列表进行修改,迭代器马上会抛出异常,快速失败。
- 该机制通过检查modCount的值来确定是否迭代过程中有其他线程对列表进行修改
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}