LinkedList : List和Deque接口的双向链表实现。它也可以被当作堆栈、队列或双端队列进行操作。
作为平时常用的集合类,其实原理并没有十分复杂。
LinkedList是非线程安全的,通过Collections.synchronizedList()可以使其变为线程安全。
下面来看一下LinkedList的实现的接口和继承关系.
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
深入查看类关系:
值得注意的是它实现了Deque接口,Deque继承的是Queue接口。所以LinkedList比List还多了 offer(),peek(),poll()等方法。
先来看一下LinkedList的成员变量
//元素个数
transient int size = 0;
//指向链表的第一个节点
transient LinkedList.Node<E> first;
//指向链表的最后一个节点
transient LinkedList.Node<E> last;
构造器:LinkedList的构造器只有两个:空参构造器和参数为集合的构造器
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
下面代码为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;
}
}
下面看一下常用方法的源码:
add () : 往List里添加元素默认在链表末尾添加调用linkLast()方法
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)
//如果LinkedList尾指针为空,即链表元素为0,将指向第一个元素的指针指向它
first = newNode;
else
l.next = newNode;
size++;
//记录了链表改变了多少次,用于fail-fast
modCount++;
}
get(int index) : 折半查找
public E get(int index) {
//检查了一下index是否超出有效范围
checkElementIndex(index);
//调用查找方法
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
//注意这里的查找是折半查找,即:先比较index和size,如果在前半部分则从头找起,如果在后半部分则从尾部找起
LinkedList.Node<E> node(int index) {
// assert isElementIndex(index);
//在前半部分则从头找起
if (index < (size >> 1)) {
LinkedList.Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {//在后半部分则从尾部找起
LinkedList.Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
在这里有个东西就必须说一下了:
LinkedList遍历:它有很多种遍历方式。这里要说明的是推荐使用迭代器遍历或ForEach。
迭代器遍历:
List<Integer> list=new LinkedList<>();
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
Integer next = iterator.next();
System.out.println(next);
}
//简写:
for(Iterator iter = list.iterator(); iter.hasNext();)
iter.next();
ForEach遍历:内部也是采用了Iterator的方式实现
for (Integer i:list)
随机遍历 :千万不要
for (int i=0; i<list.size(); i++) {
list.get(i);
}
还有pollLast,pollLast
removeFirst,removeLast(破坏链表)等
列表迭代器是快速失败的,如果在创建送代器后的任何时间以任何方式修改列表结构,除了通过列表迭代器自己的 remove或ad方法,列表送代器将抛出
ConcurrentModificationException.
remove()
public boolean remove(Object o) {
//如果传入的o为null,而List是可以存储null的
if (o == null) {
//遍历链表查找node.item为null的节点并删除
for (LinkedList.Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
//遍历链表查找node.equals的节点并删除
for (LinkedList.Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
Fail-fast:
两个线程(线程A,线程B),其中线程A负责遍历list、线程B修改list。线程A在遍历list过程的某个时候(此时expectedModCount = modCount=N),线程启动,同时线程B增加一个元素,这是modCount的值发生改变(modCount + 1 = N + 1)。线程A继续遍历执行next方法时,通告checkForComodification方法发现expectedModCount = N ,而modCount = N + 1,两者不等,这时就抛出ConcurrentModificationException 异常,从而产生fail-fast.