LinkedList
简介
本文内容均以JDK8为例。
LinkedList
是一个基于双向链表实现的集合类,经常被拿来和 ArrayList
做比较。
不过,我们在项目中一般是不会使用到 LinkedList
的,需要用到 LinkedList
的场景几乎都可以使用 ArrayList
来代替,并且,性能通常会更好!就连 LinkedList
的作者约书亚 · 布洛克(Josh Bloch)自己都说从来不会使用 LinkedList
。
另外,不要下意识地认为 LinkedList
作为链表就最适合元素增删的场景。LinkedList
仅仅在头尾插入或者删除元素的时候时间复杂度近似 O(1),其他情况增删元素的平均时间复杂度都是 O(n) ,因为都需要先遍历到对应节点再进行增删操作。
LinkedList 插入和删除元素的时间复杂度?
- 头部插入/删除:只需要修改头结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
- 尾部插入/删除:只需要修改尾结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
- 指定位置插入/删除:需要先移动到指定位置,再修改指定节点的指针完成插入/删除,因此需要移动平均 n/2 个元素,时间复杂度为 O(n)。
LinkedList 为什么不能实现 RandomAccess 接口?
RandomAccess
是一个标记接口,用来表明实现该接口的类支持随机访问(即可以通过索引快速访问元素)。由于 LinkedList
底层数据结构是链表,内存地址不连续,只能通过指针来定位,不支持随机快速访问,所以不能实现 RandomAccess
接口。
LinkedList源码分析
LinkedList
继承了 AbstractSequentialList
,而 AbstractSequentialList
又继承于 AbstractList
。
阅读过 ArrayList
的源码我们就知道,ArrayList
同样继承了 AbstractList
, 所以 LinkedList
会有大部分方法和 ArrayList
相似。
LinkedList
实现了以下接口:
List
: 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
Deque
:继承自 Queue
接口,具有双端队列的特性,支持从两端插入和删除元素,方便实现栈和队列等数据结构。需要注意,Deque
的发音为 “deck” [dɛk],这个大部分人都会读错。
Cloneable
:表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。
Serializable
: 表明它可以进行序列化和反序列化操作,也就是即可以将对象序列化为字节流进行持久化存储或网络传输,也可以从字节流反序列化为对象,非常方便。
与ArrayList不同的是,LinkedList
中的元素是通过 Node
定义的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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; } }
|
初始化
LinkedList
中有一个无参构造函数和一个有参构造函数。
1 2 3 4 5 6 7 8 9
| public LinkedList() { }
public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
|
添加元素
LinkedList
除了实现了 List
接口相关方法,还实现了 Deque
接口的很多方法,所以我们有很多种方式插入元素。
这里以 List
接口中相关的插入方法为例进行源码讲解,对应的是add()
方法。
add()
方法有两个版本:
add(E e)
:用于在 LinkedList
的尾部插入元素,即将新元素作为链表的最后一个元素,时间复杂度为 O(1)。
add(int index, E element)
:用于在指定位置插入元素。这种插入方式需要先移动到指定位置,再修改指定节点的指针完成插入/删除,因此需要移动平均 n / 2 个元素,时间复杂度为 O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public boolean add(E e) { linkLast(e); return true; }
public void add(int index, E element) { checkPositionIndex(index);
if (index == size) linkLast(element); else linkBefore(element, node(index)); }
void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
void linkBefore(E e, Node<E> succ) { final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
|
获取元素
LinkedList
获取元素相关的方法一共有 3 个:
getFirst()
:获取链表的第一个元素。
getLast()
:获取链表的最后一个元素。
get(int index)
:获取链表指定位置的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; }
public E getLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return l.item; }
public E get(int index) { checkElementIndex(index); return node(index).item; }
|
get(index)方法的核心在于 node(int index)
这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Node<E> node(int index) { if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
|
get(int index)
或 remove(int index)
等方法内部都调用了该方法来获取对应的节点。
从这个方法的源码可以看出,该方法通过比较索引值与链表 size 的一半大小来确定从链表头还是尾开始遍历。如果索引值小于 size 的一半,就从链表头开始遍历,反之从链表尾开始遍历。这样可以在较短的时间内找到目标节点,充分利用了双向链表的特性来提高效率。
删除元素
LinkedList
删除元素相关的方法一共有 5 个:
removeFirst()
:删除并返回链表的第一个元素。
removeLast()
:删除并返回链表的最后一个元素。
remove(E e)
:删除链表中首次出现的指定元素,如果不存在该元素则返回 false。
remove(int index)
:删除指定索引处的元素,并返回该元素的值。
void clear()
:移除此链表中的所有元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| 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); }
public boolean remove(Object o) { if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; }
public E remove(int index) { checkElementIndex(index); return unlink(node(index)); }
|
这里的核心在于 unlink(Node<E> x)
这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| E unlink(Node<E> x) { 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++; return element; }
|
unlink()
方法的逻辑如下:
首先获取待删除节点 x 的前驱和后继节点;
判断待删除节点是否为头节点或尾节点:
将待删除节点 x 的前驱的后继指向待删除节点的后继 next,断开 x 和 x.prev 之间的链接;
将待删除节点 x 的后继的前驱指向待删除节点的前驱 prev,断开 x 和 x.next 之间的链接;
将待删除节点 x 的元素置空,修改链表长度。
参考下图理解:
遍历链表
推荐使用for-each
循环来遍历 LinkedList
中的元素, for-each
循环最终会转换成迭代器形式。
1 2 3 4 5 6 7 8
| LinkedList<String> list = new LinkedList<>(); list.add("apple"); list.add("banana"); list.add("pear");
for (String fruit : list) { System.out.println(fruit); }
|
LinkedList
的遍历的核心就是它的迭代器的实现。
1 2 3 4 5 6 7 8 9 10 11 12
| private class ListItr implements ListIterator<E> { private Node<E> lastReturned; private Node<E> next; private int nextIndex; private int expectedModCount = modCount; ………… }
|
下面对迭代器 ListItr
中的核心方法进行详细介绍。
我们先来看下从头到尾方向的迭代:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public boolean hasNext() { return nextIndex < size; }
public E next() { checkForComodification(); if (!hasNext()) throw new NoSuchElementException(); lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; }
|
再来看一下从尾到头方向的迭代:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public boolean hasPrevious() { return nextIndex > 0; }
public E previous() { checkForComodification(); if (!hasPrevious()) throw new NoSuchElementException(); lastReturned = next = (next == null) ? last : next.prev; nextIndex--; return lastReturned.item; }
|
如果需要删除或插入元素,也可以使用迭代器进行操作。
1 2 3 4 5 6 7 8 9 10 11
| LinkedList<String> list = new LinkedList<>(); list.add("apple"); list.add(null); list.add("banana");
list.removeIf(Objects::isNull);
for (String fruit : list) { System.out.println(fruit); }
|
迭代器对应的移除元素的方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public void remove() { checkForComodification(); if (lastReturned == null) throw new IllegalStateException();
Node<E> lastNext = lastReturned.next; unlink(lastReturned); if (next == lastReturned) next = lastNext; else nextIndex--; lastReturned = null; expectedModCount++; }
|