与ArrayList同为List,LinkedList却展现出不同的特性。作为java.util下的另一重要容器,我们下面来探究一下LinkedList的源码实现及特性分析。
上篇文章讲述到,ArrayList用数组来存储数据,伴随数据量的变大,ArrayList动态扩充数组容量。
与之不同,LinkedList使用链表来存储数据,因此它在插入/删除数据方面有着天然的优势,而在读取指定位置的元素时,性能却不及ArrayList速度快。
LinkedList存储元素的变量如下:
transient Node<E> first;
存储数据的节点: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; //后节点
}
}
可以看出来,所有元素节点连在一起,构成了双向链表(prev指向前一个元素节点,next指向后一个元素节点)。所有数据,保存在Node的item中。相比于单向链表,双向链表提供了两种操作的方向,因此可以提供更快的遍历查找速度,更快的插入、删除元素速度(代价是提升了链表维护的难度,以及一点点存储引用的空间)。
下面看一下LinkedList内部对于双向链表的维护:
在此之前,需要知道的一点:LinkedList额外保存了first和last两个节点,分别指向首位和末尾,作为双向链表的两端入口。
内部维护链表的一系列函数如下(private、protected):
//用e构造Node,将数据插到首位
private void linkFirst(E e)
{
final Node<E> f = first; //取得首位节点node
final Node<E> newNode = new Node<>(null, e, f); //构造节点,item为e,prev为null,next指向当前的首位f
first = newNode; //将first指向newNode,以它作为新的首位
if (f == null)
last = newNode; //若之前的首位f不存在,则新的首位节点newNode在作为first的同时,也是last
else
f.prev = newNode; //若之前的首位f存在,则将f的prev指向新的首位节点newNode
size++;
modCount++;
}
//用e构造Node,将数据插到末尾,原理类似
void linkLast(E e);
//将节点插入到指定节点前
void linkBefore(E e, Node<E> succ)
{
final Node<E> pred = succ.prev; //取得原先succ之前的节点pred,现在需要将newNode插在succ之前,pred之后
final Node<E> newNode = new Node<>(pred, e, succ); //构造内容为e的newNode,prev指向pred,next指向succ
succ.prev = newNode; //将succ的prev指向newNode
if (pred == null)
first = newNode; //若pred为空,则将newNode置为first
else
pred.next = newNode; //将pred的next指向newNode
size++;
modCount++;
}
//将首位first节点从链表去掉
private E unlinkFirst(Node<E> f);
//将末位last节点从链表去掉
private E unlinkLast(Node<E> l);
//将任意位置的节点x从链表去掉
E unlink(Node<E> x)
{
//分别考虑prev与next为null和非null的情况,修复prev与next的指向
}
基于上述链表操作函数,LinkedList开放了如下接口(public)
public E getFirst()
{
//取得first内部的item,返回
}
public E getLast()
{
//取得last内部的item返回
}
public E removeFirst()
{
//调用unlinkFirst(Node<E> f), 将first从链表移除
}
public E removeLast()
{
//调用unlinkLast(Node<E> l), 将last从链表移除
}
public void addFirst(E e)
{
//将e插入到first之前
}
public void addLast(E e)
{
//将e插入到last之后
}
public boolean add(E e)
{
//调用linkLast(e),将e插入到last之后
}
public boolean remove(Object o)
{
//从first开始遍历链表,找到o,移除节点
}
public int indexOf(Object o)
{
//从first开始遍历节点,找到o,返回index
}
public int lastIndexOf(Object o)
{
//从last开始反向遍历链表,找到o,移除链表
}
public boolean contains(Object o)
{
//从first开始遍历链表,找到o,返回true;或者找不到o,返回false
}
public boolean addAll(Collection<? extends E> c)
{
//1. 将集合c转成数组
//2. 遍历数组,对于每个元素,构造node,链在last后面
}
总结:LinkedList用双向链表维护元素,相比于ArrayList提供了快速的插入、移除数据操作的同时,也比单向链表的遍历查找速度更快。但是,相比于ArrayList,LinkedList的查找指定index元素效率低(ArrayList使用数组存储数据,可以直接依据索引读取)。