一、LinkedList介绍
LinkedList继承自AbstractSequenceList,实现了Deque和List接口,它的底层实现是一个双向链表。可以作为栈、队列的实现去使用。LinkedList不是线程安全的,如果存在多线程访问LinkedList且至少有一个线程对LinkedList做出结构性改变,那么该数据结构需要做额外的数据同步,通常的做法是使用Collections.synchronizedList包装该类对象或者使用JUC(java.util.concurrent)包里的线程安全容器类如CopyOnWriteArrayList。
二、LinkedList的数据结构
1 - 继承结构
AbstractCollection
|___AbstractList
|___AbstractSequenceList
|___LinkedList
2 - 类数据结构
package java.util;
import java.util.function.Consumer;
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
//容器存储的元素数量
transient int size = 0;
//头节点,指向第一个元素
transient Node<E> first;
//尾节点指向最后一个元素
transient Node<E> last;
}
分析LinkedList的内部数据结构显而易见这是一个双端链表结构
三、LinkedList的源码分析
1 - 构造方法
//无参构造方法
public LinkedList() {
}
//带集合参数构造方法
public LinkedList(Collection<? extends E> c) {
this();
//将集合中的所有元素添加到LinkedList容器中
addAll(c);
}
2 - 其他成员方法
//在LinkedList链表头部插入元素
private void linkFirst(E e) {
final Node<E> f = first;
//根据指定元素e创建一个新节点
final Node<E> newNode = new Node<>(null, e, f);
//头节点指向新节点
first = newNode;
//如果头节点为空即LinkedList为空->原链表元素为空,尾节点也指向当前新节点
if (f == null)
last = newNode;
else//否则链表元素不为空,原头节点的前置节点prev指向当前新节点
f.prev = newNode;
size++;
modCount++;
}
//LinkedList链表尾部插入指定元素e
void linkLast(E e) {
final Node<E> l = last;
//根据指定元素e创建新节点
final Node<E> newNode = new Node<>(l, e, null);
//尾节点last指向当前新节点
last = newNode;
//如果尾节点为空则原链表元素为空,头节点指向当前新节点
if (l == null)
first = newNode;
else//否则原链表元素不为空,原尾节点的后置节点next指向当前节点
l.next = newNode;
size++;
modCount++;
}
//在指定节点succ之前插入指定元素e
void linkBefore(E e, Node<E> succ) {
//获取指定节点succ的前置节点,如果succ为空抛出异常
final Node<E> pred = succ.prev;
//根据指定元素e创建新节点,并指定新节点的前置和后置节点
final Node<E> newNode = new Node<>(pred, e, succ);
//指定节点succ前置节点指向新节点
succ.prev = newNode;
//如果succ前置节点为空则它是头部节点,在头部节点之前插入新节点则新节点成为新的头部节点
if (pred == null)
first = newNode;
else//否则,指定节点succ不是头节点,让succ原前置节点pred指向新节点
pred.next = newNode;
size++;
modCount++;
}
//删除并返回头节点元素
private E unlinkFirst(Node<E> f) {
//获取指定节点f包含的元素
final E element = f.item;
//获取指定节点f的后置节点
final Node<E> next = f.next;
f.item = null;
//后置节点置空,f之前部分链表断开,有助于垃圾回收器回收对象
f.next = null;
//头部节点指向指定节点f的后置节点
first = next;
//若后置节点为空则链表已空,尾节点last设置为null
if (next == null)
last = null;
else//否则新的头节点的前置节点prev设置为null
next.prev = null;
//元素个数计数器-1
size--;
//容器修改计数器
modCount++;
return element;
}
//删除并返回尾节点元素
private E unlinkLast(Node<E> l) {
//获取当前尾节点的元素element
final E element = l.item;
//获取尾节点的前置节点prev
final Node<E> prev = l.prev;
//尾节点置空
l.item = null;
l.prev = null;
//尾节点指向原尾节点的前置节点prev
last = prev;
//若前置节点为空则原LinkedList链表已空,头节点first设置为null
if (prev == null)
first = null;
else//否则原链表不为空,新的尾节点指向的后置节点next设置为null
prev.next = null;
size--;
modCount++;
return element;
}
//删除指定节点x,该方法是后续LinkedList元素删除操作的核心方法会在下文进行重点分析
E unlink(Node<E> x) {
//获取指定节点的元素element
final E element = x.item;
//获取元素指向的后置节点next
final Node<E> next = x.next;
//获取元素指向的前置节点prev
final Node<E> prev = x.prev;
//若前置节点指向为空则删除的元素节点是头节点,将first头部节点指向方法指定节点x的后置节点
if (prev == null) {
first = next;
}
//若不是头部节点,那么指定删除节点x的前置节点的后置节点绕过它指向它的下一个节点,节点x的前置节点指向null
else{
prev.next = next;
x.prev = null;
}
//若被删除节点x是尾节点,则last指向x前置节点prev
if (next == null) {
last = prev;
}
//否则被删除节点x后置节点的前置节点绕过它指向它的前置节点,被删除节点的后置节点设置为null
else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
//获取LinkedList容器第一个元素
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
//获取LinkedList容器最后一个元素
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
//删除容器第一个元素
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//删除元素最后一个元素
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
//删除尾节点并返回尾节点元素,与上述removeLast方法的区别只在于当LinkedList为空本方法直接返回null,
//而不是直接抛出NoSuchElementException异常
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
//在LinkedList链表头部新增指定元素e
public void addFirst(E e) {
linkFirst(e);
}
/在LinkedList链表尾部新增元素e
public void addLast(E e) {
linkLast(e);
}
public boolean contains(Object o) {
return indexOf(o) != -1;
}
//获取容器中保存的元素数量,也可以说是链表节点长度
public int size() {
return size;
}
//在LinkedList链表尾部插入一个元素e
public boolean add(E e) {
linkLast(e);
return true;
}
//从前往后删除链表中第一个出现的元素对象o
public boolean remove(Object o) {
//如果o为空删除链表中第一个元素为空的节点
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
}
//否则,调用对象的equals方法,删除链表中元素等于o的节点信息
else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
//将集合c中所有元素插入LinkedList尾部
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//在指定索引位置index插入集合c中所有元素
public boolean addAll(int index, Collection<? extends E> c) {
//指定索引位置index合法性校验(0 <= index <= size)
checkPositionIndex(index);
//集合转数组
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
//如果插入索引位置index在LinkedList尾部即将插入的下一个位置
if (index == size) {
succ = null;
pred = last;
} else//否则插入索引位置index落在LinkedList链表内 {
succ = node(index);
pred = succ.prev;
}
//遍历集合c创建节点并连接到指定索引位置index-1节点后
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;
}
如果原来指定索引位置index处节点为空,则该节点为尾节点,直接让尾节点指向最后的新增节点pred
if (succ == null) {
last = pred;
} else//如果指定索引位置的节点不是尾节点,最后新增节点指向原索引位置节点succ,原索引位置节点的前置节点指
//向最后新增节点pred {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
//从头节点first开始从头到尾遍历链表删除链表节点
public void clear() {
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指定索引位置index处的元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
//将指定索引位置index处的元素值设置为指定新元素element,返回原来的节点元素
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
//在指定索引位置处插入指定新元素element
public void add(int index, E element) {
//索引位置index合法性校验
checkPositionIndex(index);
//在链表尾部的下一个节点处插入
if (index == size)
linkLast(element);
else//在链表内插入
linkBefore(element, node(index));
}
//删除指定位置元素index
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//判断索引位置index是否在元素节点范围
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
//判断传入的索引参数index是否有效,通常在迭代器或者元素新增操作用于判断操作位置索引是否有效
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
//校验传入的元素索引index是否合法(即0 <= index <size),如果非法怕抛数组越界异常
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//校验传入的索引位置参数index是否合法(即0 <= index <=size)通常用于在迭代器生成和新增元素时判断操作元素的索
//引位置是否合法,非法则抛出数组越界异常
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//返回LinkedList指定索引位置index处的节点
Node<E> node(int index) {
//若index小于数组的一半从头节点开始从前往后遍历获取指定位置index处节点,这里如果调用方方法不做索引index合
//法性校验,传入一个负数,那么方法会报空指针异常
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
}
//若大于等于数组的一半则从尾节点从后往前遍历获取指定位置index处节点
else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//返回LinkedList中指定元素o第一次出现的位置,若返回-1表示集合中不存在该元素
public int indexOf(Object o) {
int index = 0;
//若o为null,从头节点first开始从前往后遍历链表返回第一个元素对象为null的元素索引下标
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
}
//若o不为null,从头节点first开始从前往后遍历链表当节点x满足o.equals(x.item)方法结束返回x节点的元素索引
//下标
else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
//LinkedList不包含元素o返回-1
return -1;
}
//与index方法逻辑基本相同,只是遍历LinkedList链表的方向不同,本方法选取尾节点last为遍历起点,从后往前遍历链表
//节点
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
//返回LinkedList头节点元素
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//返回LinkedList的头节点元素,与上述方法peek的不同点只在于peek方法若链表为空返回null而本方法会抛出
//NoSuchElementException异常
public E element() {
return getFirst();
}
//删除头节点,并返回头节点元素
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//删除头节点,并返回头节点元素,与上述poll方法的区别只在于poll方法遇到LinkedList为空直接返回null不会抛异
//常,而remove会,碰到空链表会抛NoSuchElementException异常
public E remove() {
return removeFirst();
}
//LinkedList尾节点之后插入指定元素e,成功返回true
public boolean offer(E e) {
return add(e);
}
//LinkedList头节点之前插入指定元素e,成功返回true
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
//在LinekdList头节点之前插入指定元素e,无返回
public void push(E e) {
addFirst(e);
}
//LinkedList尾节点之后插入指定元素e
public boolean offerLast(E e) {
addLast(e);
return true;
}
//返回头节点元素,方法实现与peek方法一模一样
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//返回LinkedList尾节点元素,如果链表为空则返回null否则返回尾节点对应元素last.item
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
//删除头节点,并返回头节点的元素item
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//删除尾节点,并返回尾节点元素item
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
//删除头节点,并返回头节点元素
public E pop() {
return removeFirst();
}
//从LinkedList头节点开始从前往后遍历删除第一个元素item等于指定对象o的节点,若成功则返回true失败返回false
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
//从LinkList尾节点开始从后往前遍历删除第一个元素item等于指定对象o的节点,若成功返回true,失败返回false
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
//集合中不包含元素o
return false;
}
//返回LinkedList中指定索引位置index处对应的的List迭代器,返回的是一个ListItr(LinkedList中ListIterator的
//内部实现类)对象
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
//返回一个反向迭代器DescendingIterator对象,该迭代器起点是LinkedList尾节点,遍历顺序为反向遍历
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
//调用父类拷贝函数,若父类不支持则抛出内部错误
@SuppressWarnings("unchecked")
private LinkedList<E> superClone() {
try {
return (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
//本类拷贝函数,浅拷贝
public Object clone() {
LinkedList<E> clone = superClone();
// Put clone into "virgin" state
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
// Initialize clone with our elements
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
//将LinkedList容器中所有的元素填入一个对象数组中返回
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
//将LinkedList容器中所有的元素填入指定对象数组a中并返回
public <T> T[] toArray(T[] a) {
//如果指定对象数组a长度小于LinkedList中双向链表的节点个数size则重新分配一个新的数组给a,新数组数组长度为
//size
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
//如果指定的对象数组长度大于LinkedList的双向链表节点个数则溢出部分数组项设置为null
if (a.length > size)
a[size] = null;
return a;
}
//在LinkedList序列化时调用,先是写入元素个数,再循环写入元素集合
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);
}
//在反序列时调用,先读取元素个数size,然后循环读取元素将由元素生成的节点插入到LinkedList链表中
@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());
}
}
通过对源码的分析我们可以发现LinkedList暴露给外界的成员方法基本都依赖于linkBefore、unLink、indexOf等方法,下面对这几个方法做重点分析。
1)void linkFirst(E e)方法
//在LinkedList链表头部插入元素
private void linkFirst(E e) {
final Node<E> f = first;
//根据指定元素e创建一个新节点
final Node<E> newNode = new Node<>(null, e, f);
//头节点指向新节点
first = newNode;
//若原头节点为空即LinkedList为空->原链表元素为空,尾节点也指向当前新节点
if (f == null)
last = newNode;
else//否则链表元素不为空,原头节点的前置节点prev指向当前新节点
f.prev = newNode;
size++;
modCount++;
}
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;
}
}
linkFirst方法的主要作用是在LinkedList内部维护的双向链表在头部节点处插入一个指定元素,主要应用在内部成员方法addFirst中。这里我们通过图形文字结合的方法分析一下该方法的内部逻辑。
1、根据指定元素e创建新节点newNode并初始化节点对象,我们跟进Node<>(null, e, f)方法发现方法里面主要设置了节点元素item,并让它的前置节点指向null后置节点指向原头节点,如图1-1;
2、LinkedList头节点first指向当前新节点newNode;
3、若原来的头部节点为空则说明原链表为空链表,则头节点first和尾节点last均指向新节点;否则原链表不为空,原头节点的前置节点指向新的头节点newNode;
4、元素计数器size+1,修改计数器modCount+1方法结束;
2)E unlink(Node<E> x)方法
E unlink(Node<E> x) {
//获取指定节点的元素element
final E element = x.item;
//获取元素指向的后置节点next
final Node<E> next = x.next;
//获取元素指向的前置节点prev
final Node<E> prev = x.prev;
//若前置节点指向为空则删除的元素节点是头节点,将first头部节点指向方法指定节点x的后置节点
if (prev == null) {
first = next;
}
//若不是头部节点,那么指定删除节点x的前置节点的后置节点绕过它指向它的下一个节点,节点x的前置节点指向null
else{
prev.next = next;
x.prev = null;
}
//若被删除节点x是尾节点,则last指向x前置节点prev
if (next == null) {
last = prev;
}
//否则被删除节点x后置节点的前置节点绕过它指向它的前置节点,被删除节点的后置节点设置为null
else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
此方法是LinkedList所有涉及元素删除方法的核心,例如removeLast、pollLast、removeFirst、pollFirst、poll、remove等,unlinkFirst和unlinkLast只是该方法的特殊实现因此只需要理清该方法逻辑,所有与删除相关的成员方法逻辑就基本清晰了。
以下是该方法的主要流程逻辑:
1、获取指定节点x的前置节点prev和后置节点next;
2、
1)若删除的节点x是头节点,则新的头节点first指向节点x的后置节点next,后置节点next的前置节点next.prev指向null,节点x的后置节点指向null,如图2-1;
2)若删除的节点x是尾节点,则尾节点后置节点指向null,尾节点前置节点指向null,新的尾节点last指向指定节点x的前置节点,如图2-2;
3)若删除的节点x既不是first也不是last而是链表之间节点,则先将指定节点x的前置节点prev的后置节点prev.next指向指定节点x的后置节点next,指定节点x的前置节点指向null,指向节点x的后置节点next的前置节点next.prev指向前置节点prev,指定节点x的后置节点指向null,如图2-3。
3、节点x指向的元素x.item设置为null,元素个数size+1,容器修改计数器+1,结束返回节点x的元素。
3 - LinkedList总结
1)LinkedList底层是通过一个双向链表去实现的,没有容量限制(当然肯定不能大于Integer.MAX_VALUE),链表节点类Node成员变量包括当前节点的元素element,前置节点prev和后置节点next;
2)LinkedList也实现了List接口因此它也支持随机访问,但是因为内部存储结构是链表顺序访问的效率远比随机访问快,随机访问(根据索引访问)主要是通过在方法内部维护一个索引计数器实现,他首先会比较指定索引index与双向链表长度的1/2,若index大于链表长度的1/2那么从头节点first往后遍历直到索引计数器等于index,否则从尾节点往前遍历,直到索引计数器等于index;
3)LinkeList实现了Deque接口,在方法内部提供了在链表两端插入、删除和检查元素的方法,每种方法都包括两种形式,一种在操作失败(由于操作异常链表)时抛出异常,另一种返回一个特殊值(false或者null);
4)LinkedList通过迭代器遍历集合并进行操作时如果有其他线程在这过程中对该集合做出了结构性修改,那么会直接抛出ConcurrentModification异常,这就是java集合中常说的错误检测机制“fail fast”,它主要通过比较当前线程根据集合生成迭代器时的集合修改次数expectModCount和当前集合修改次数modCount判断是否存在其他线程修改集合结构。