1.概述
LinkedList
是基于双向链表来编写的,不需要考虑容量的问题,但节点需要额外的空间存储前驱和后继的引用。有序可重复,可存储null
值,非线程安全。
1.1继承体系
LinkedList
实现了Deque
接口,这样LinkedList
就具备了双端队列的功能,本文旨在介绍LinkedList
的List
功能,队列功能不再进行说明。
LinkedList
没有实现RandomAccess
接口,所以不要通过访问下标方式(for(int i=0;i
2.源码分析
本文主要针对LinkedList
的常用操作进行分析,代码如下。
List linkedList = new LinkedList<>();
//add(E e)
linkedList.add("QQ");
linkedList.add("WW");
linkedList.add("EE");
linkedList.add("RR");
linkedList.add("TT");
linkedList.add("YY");
linkedList.add("UU");
linkedList.add("II");
linkedList.add("OO");
//add(int index, E element)插入元素到指定结点
linkedList.add(2, "BaseC");
//get
System.out.println(linkedList.get(2));
//traverse(遍历)
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()) {
iterator.next();
}
//remove(Object o)
linkedList.remove("EE");
//remove(int index)删除中间元素
linkedList.remove(3);
//remove(int index)删除链表头元素
linkedList.remove(0);
//remove(int index)删除链表尾元素
linkedList.remove(linkedList.size() - 1);
System.out.println(linkedList);
2.1属性
//元素个数
transient int size = 0;
//链表首结点
transient Node first;
//链表尾结点
transient Node last;
//链表结点类
private static class Node {
//结点中的值
E item;
//指向之前的节点
Node prev;
//指向之后的节点
Node next;
//构造方法
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2.2新增
2.2.1add(E e)
//将元素添加到列表尾部
public boolean add(E e) {
linkLast(e);
return true;
}
//将e元素添加链表末端
void linkLast(E e) {
//声明l变量保存当前last对象
final Node l = last;
//以e为item声明一个新的last对象
final Node newNode = new Node<>(l, e, null);
last = newNode;
//第一次添加元素时
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
首次添加元素如下图所示:
非首次添加元素如下如图所示:
2.2.2add(int index, E element)
//将元素插入到指定index,之前在此index及其之后的元素向右移动。
public void add(int index, E element) {
//判断index是否在[0,size]之间,注意包含0和size。
checkPositionIndex(index);
//如果index等于当前size,调用linkLast(E e)方法,否则调用linkBefore(E e)方法
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
//返回指定index上的非空结点
Node node(int index) {
//假设index合法
//size>>1返回size的一半,判断index是在链表的前半部分还是后半部分,提高查询效率。
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//在非空的succ结点之前插入元素
void linkBefore(E e, Node succ) {
//获取succ结点的prev结点
final Node pred = succ.prev;
//在pred和succ之间生成item为e的结点
final Node newNode = new Node<>(pred, e, succ);
//将succ的prev指向newNode
succ.prev = newNode;
//如果succ是头元素,将first指向newNode
if (pred == null)
first = newNode;
else
//否则将pred的next结点指向newNode
pred.next = newNode;
size++;
modCount++;
}
添加流程如下图所示:
在链表首尾添加元素很高效,时间复杂度为O(1)
。
在链表中间添加元素比较低效,时间复杂度为O(N)
。
2.3查找
//返回指定位置上的值
public E get(int index) {
//检查index是否合法
checkElementIndex(index);
//node(int index)方法已在上文列出,此处不在展示。
return node(index).item;
}
查找方法的重点在于node(int index)
方法。由于需要通过从头或从尾查找元素,时间复杂度为O(N)
。
2.4遍历
在分析源码之前,先看下iterator()
的调用流程。首先调用linkedList.iterator()
进入AbstractSequentialList
的iterator()
方法。
public Iterator iterator() {
return listIterator();
}
此方法调用了其父类AbstractList
的listIterator()
方法。
public ListIterator listIterator() {
return listIterator(0);
}
而在此方法中调用了listIterator(final int index)
方法,此方法LinkedList
将其重写了,所以程序就会去调用LinkedList
中的listIterator(int index)
方法。通过这个流程我们也巩固下Java
多继承下方法的调用流程。下面咱们就来看源码吧。
public ListIterator listIterator(int index) {
//判断index是否在[0,size]之间,注意包含0和size。
checkPositionIndex(index);
return new ListItr(index);
}
private class ListItr implements ListIterator {
//上一次返回的值
private Node lastReturned;
//当前要返回的值
private Node next;
//当前index值
private int nextIndex;
//用于fail-fast校验
private int expectedModCount = modCount;
ListItr(int index) {
//如果index是当前size值,则next为null,否则返回对应index的结点。
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
//fail-fast判断
checkForComodification();
//判断当前是否还有值,调用方可直接调用next()方法获取下个值,不必额外进行hasNext()的判断。
if (!hasNext())
throw new NoSuchElementException();
//即将返回next,将next传给lastReturned
lastReturned = next;
//获取下次将要返回的结点
next = next.next;
nextIndex++;
return lastReturned.item;
}
//省略其余代码。。。
}
2.5删除
2.5.1remove(Object o)
//删除o在列表中第一次存储的位置
public boolean remove(Object o) {
//当o为null时
if (o == null) {
for (Node x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
//删除非空结点
E unlink(Node x) {
// assert x != null;
final E element = x.item;
final Node next = x.next;
final Node prev = x.prev;
//删除头元素时
if (prev == null) {
first = next;
} else {
//将prev结点的next指针指向next结点,将被删除结点的prev置为null
prev.next = next;
x.prev = null;
}
//删除尾元素时
if (next == null) {
last = prev;
} else {
//将next结点的prev指针指向prev结点,将被删除结点的next置为null
next.prev = prev;
x.next = null;
}
//将x的item变为null,便于GC
x.item = null;
size--;
modCount++;
return element;
}
unlink(Node x)
其流程如下图所示:
2.5.2remove(int index)
//删除指定index处的元素,后面的元素向左移动一位,返回被删除的元素。
public E remove(int index) {
//检验index是否在[0,index)之内
checkElementIndex(index);
//通过node(int index)查找结点,将其传入unlink(Node node)方法
return unlink(node(index));
}
在链表首尾删除元素很高效,时间复杂度为O(1)
。
在链表中间删除元素比较低效,时间复杂度为O(N)
。