LinkedList同时实现了List接口和Deque对口,也就是它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(stack)。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
//...
}
当你需要使用栈或者队列时,可以考虑用LinkedList,一方面是因为Java官方已经声明不建议使用Stack类,更遗憾的是,Java里根本没有一个叫做Queue的类(只是一个接口的名字)。
关于栈或队列,现在首选是ArrayDeque,它有着比LinkedList(当作栈或队列使用时)更好的性能。
LinkedList 没有实现 RandomAccess 接口其底层基于链表结构,无法向 ArrayList 那样随机访问指定位置的元素。LinkedList 查找过程要稍麻烦一些,需要从链表头结点(或尾节点)向后(向前)查找,时间复杂度为 O(N)。这是因为 LinkedList 存储数据的内存地址是不连续的,所以不支持随机访问。
【1】类与数据结构
① 类继承
如下所示其继承自AbstractSequentialList,实现了List、Deque、Cloneable以及Serializable。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
//...
}
实现了Cloneable以及Serializable标明其是可以克隆及序列化的。实现了List、Deque
标明其是可以作为链表或者队列使用。那么AbstractSequentialList是什么呢、
从实现上,AbstractSequentialList 提供了一套基于顺序访问的接口。通过继承此类,子类仅需实现部分代码即可拥有完整的一套访问某种序列表(比如链表)的接口。
AbstractSequentialList 实现顺序访问是通过listIterator实现的,如下get方法。
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
LinkedList实现了父类AbstractSequentialList 的抽象方法listIterator。
public abstract ListIterator<E> listIterator(int index);
② 数据结构
LinkedList 的数据结构示意图
核心属性如下所示,first和last是两个指针结点,分别表示第一个和最后一个结点。
// 元素个数
transient int size = 0;
//头指针
transient Node<E> first;
//尾指针
transient Node<E> last;
与ArrayList不同,这里没有数组。而是通过一个个Node关联起来。如下所示,Node中包含了next和prev,形成了双向链表。
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;
}
}
【2】核心方法
① 添加元素
默认添加到链表末尾。
public boolean add(E e) {
linkLast(e);
return true;
}
linkLast方法会对first和last进行初始化,然后把元素追加到链表后面,更新size和modCount。
void linkLast(E e) {
//第一次其是null
final Node<E> l = last;
// Node(Node<E> prev, E element, Node<E> next)
//实例化新结点 prev-> l ,next->null
final Node<E> newNode = new Node<>(l, e, null);
//newNode作为last
last = newNode;
if (l == null)
//newNode也作为first
first = newNode;
else
l.next = newNode;
//元素数量+1
size++;
// 结构变化计数器+1
modCount++;
}
② 指定位置添加元素
如果索引位置等于size,那么直接追加到链表末尾。这种情况比较高效,否则就需要遍历链表找到index位置,插入数据element。
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
checkPositionIndex同样是用来检测index是否合法的。
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
node(index)
用来检索目标位置的结点,返回结果是Node。如果index 小于size的一半,则从first开始正向检索,否则反向检索。
Node<E> node(int index) {
// assert isElementIndex(index);
//如果index 小于size的一半,则从first开始正向检索
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//否则从last开始反向检索
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
linkBefore(E e, Node<E> succ)
是将元素e插入到结点succe前面。
// Node<E> succ是索引位置的元素,E e是将要插入的元素
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//获取当前结点的头结点
final Node<E> pred = succ.prev;
//将e实例化结点 Node(Node<E> prev, E element, Node<E> next)
final Node<E> newNode = new Node<>(pred, e, succ);
//当前结点prev指向新结点
succ.prev = newNode;
if (pred == null)//判断是否为第一个元素
first = newNode;
else
//非第一个元素那么当前结点的前一个结点.next指向新结点,完成结点的插入
pred.next = newNode;
//更新size modCount
size++;
modCount++;
}
③ 获取指定位置元素
其首先判断index,然后基于node(index)方法实现查找。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
//checkElementIndex用来判断index是否合法,[0,size)
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
④ 移除元素remove()
不指定的情况下,默认移除链表的第一个元素。
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
⑤ unlinkFirst
unlinkFirst也就是把头结点从链表中隔断。
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
//获取结点f的下一个结点
final Node<E> next = f.next;
//f.prev本身就是null,这里等于将first置为null,帮助垃圾回收
f.item = null;
f.next = null; // help GC
//把头结点指向f的下一个结点
first = next;
if (next == null)//说明目前链表只有一个结点,那么first=last=null
last = null;
else
next.prev = null;//否则把当前头结点的prev指向null
//更新size modCount
size--;
modCount++;
return element;
}
⑥ 移除指定位置元素
checkElementIndex不再赘述,node(index)用来检索目标位置的结点。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
这里实际上交给了unlink(Node<E> x)
来处理。
unlink(Node)
这里核心思想是让当前结点的前后结点形成双向绑定关系,然后将当前结点的item、next 、 prev置为null帮助GC。
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
size--;
modCount++;
//返回移除的结点中的元素
return element;
}
⑦ 移除指定对象
remove(Object o)的核心思想就是从first开始遍历,找到期望的结点然后交给unlink(x)
完成结点移除操作。
public boolean remove(Object o) {
// 当 o 为null时,使用 == 判断
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
// 当 o 不为null,使用equals判断
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
【3】实现Deque接口的几个方法
① poll
获取并移除链表的头结点。如下所示,LinkedList交给了unlinkFirst来实现。
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
pollFirst等价于poll,检索并移除头结点
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
pollLast则是检索并移除尾结点
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
② peek
这个方法与element()方法一样,都是获取链表的头结点(并不移除哦)。
// 返回头结点中的item,也就是element
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E element() {
return getFirst();
}
peekFirst等价于peek,都是获取头结点的元素。
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
peekLast获取尾结点的元素。
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
③ pop
从该链表表示的堆栈中弹出一个元素。在LinkedList其实也就是移除并返回链表的头结点(中的element)与removeFirst()方法等价。
public E pop() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
④ push
对于LinkedList来说,其是向头结点插入数据。
public void push(E e) {
addFirst(e);
}
public void addFirst(E e) {
linkFirst(e);
}
linkFirst
private void linkFirst(E e) {
final Node<E> f = first;
// Node(prev,item,next)
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)//判断是否只有一个结点
last = newNode;
else
f.prev = newNode;
//更新size modCount
size++;
modCount++;
}
⑤ offer
向链表尾部追加结点,等价于offerLast方法。
public boolean offer(E e) {
return add(e);
}
对应的同样有offerFirst和offerLast,分别等价于linkFirst和linkLast。
//其实就是 linkFirst(e);
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
//其实就是linkLast
public boolean offerLast(E e) {
addLast(e);
return true;
}