LinkedList源码解析
1.整体架构
LinkedList底层的数据结构是一个双向链表,结构如下图,
双向链表结构中每个节点可以向前和向后追溯,该结构的几个特点需要注意,
- 链表中每个节点是Node类对象
- first表示双向链表头节点,其前一个节点为null
- last表示双向链表尾节点,其后一个节点为null
- 当链表中没有数据时,first和last是指向同一个Node对象,其前后节点都是null
- 双向链表区别于数组,只要机器的内存足够大,并没有大小的限制
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;
}
}
该类是LinkedList类的一个内部类,其中三个成员变量的说明如下,
变量类型 | 变量名 | 说明 |
---|---|---|
E(泛型) | item | 节点值 |
Node<E> | next | 下一个节点对象 |
Node<E> | prev | 前一个节点对象 |
LinkedList实现了Queue接口和Deque接口,在添加、删除和查找元素时有不同的方法可以使用。
2.源码解析
查询节点(依据索引)
链表中查询某个节点是比较慢的,需要对链表进行遍历,使用的是LinkedList类node方法,
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// 如果 index 处于队列的前半部分,从头开始找;
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { // 如果 index 处于队列的后半部分,从尾开始找
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
LinkedList在查找节点时并没有采用从头循环到尾,而是采取了简单的二分法。使得循环次数减半,提高了查询性能。这种设计方式值得借鉴。
LinkedList查找元素方法调用形式
LinkedList实现的Queue接口提供了两种元素查找方法,用于取出头节点item值。在链表为空时返回值不同,
方法 | 返回值类型 | 空链表情况 |
---|---|---|
element | E | 抛出异常 |
peek | E | 返回null |
LinkedList实现了List接口的get方法用于获取指定位置节点的item值,
方法 | 返回值类型 | 空链表情况 |
---|---|---|
get(int index) | E | 获取指定索引处节点的item值 |
LinkedList实现额Deque接口支持从头尾两种方向获取元素,
方法 | 返回值类型 | 空链表情况 |
---|---|---|
getFirst | E | 获取头节点的item值,链表为空会抛出异常 |
getLast | E | 获取尾节点的item值,链表为空会抛出异常 |
- element方法
/**
* 检索并返回链表头节点(不删除)
* @return the head of this list
* @throws NoSuchElementException if this list is empty
*/
public E element() {
return getFirst();
}
/**
* Returns the first element in this list.
* @return the first element in this list
* @throws NoSuchElementException if this list is empty
*/
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
- peek方法
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
- get方法
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
- getFirst方法
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
- getLast方法
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
添加元素
LinkedList添加元素的操作都是链表操作,有三种操作形式分别在头部、尾部和中间指定位置添加。
linkLast方法尾部添加
完整的过程可以表示为,
/**
* Links e as last element.
*/
void linkLast(E e) {
// l是当前的尾节点,newNode是追加节点(未来的尾节点)
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
// 如果l是null,表示之前双向链表中没有数据,则追加后,first也会指向newNode
if (l == null)
first = newNode;
else
l.next = newNode;
// 更新大小和版本信息
size++;
modCount++;
}
尾部追加节点过程很简单,只需要简单的把指向位置修改即可。
linkFirst方法头部添加
/**
* Links e as first element.
*/
private void linkFirst(E e) {
// f是当前链表头节点,newNode是添加到头部后链表新的头节点
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
// 如果原本的头节点为null,说明之前链表中没有数据,则尾节点last也要指向newNode
if (f == null)
last = newNode;
else
f.prev = newNode;
// 更新大小和版本信息
size++;
modCount++;
}
头部追加节点是对原本链表头节点的prev变量的引用进行修改。
linkBefore方法添加到指定节点前
linkBefore方法指定将节点添加到链表指定节点前,
/**
* 指定添加到某个索引位置上
*/
void linkBefore(E e, Node<E> succ) {
// 获取指定节点succ的prev对象,并将succ的prev指向newNode
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
// 如果pred为null,说明succ是头节点,则first指向新的头节点newNode;反之,pred的next指向newNode
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
LinkedList的添加元素方法调用形式
LinkedList实现了List接口,
调用形式 | 返回值类型 | 说明 |
---|---|---|
add(E e) | boolean | 默认添加到链表尾部,方法调用linkLast方法 |
add(int index, E element) | void | 添加到链表指定索引位置,方法调用linkBefore方法 |
LinkedList实现Queue接口和Deque接口后新增的添加元素的方法,
调用形式 | 返回值类型 | 说明 |
---|---|---|
offer(e) | boolean | 添加指定元素到链表尾部 |
addFirst(e) | void | 添加元素到链表头部,方法调用linkFirst方法 |
offerFirst(e) | boolean | 添加指定元素到链表头部,方法调用addFirst |
addLast(e) | void | 添加元素到链表尾部,方法调用linkLast方法 |
offerLast(e) | boolean | 添加指定元素到链表尾部,方法调用addLast |
删除节点
节点删除与追加类似同样定义了3种删除方法,删除过程是将节点的item、prev和next成员变量都指向null,有助于GC。
unlinkFirst方法头部删除节点
removeFirst方法删除头部节点并返回头节点的item值,
/**
* Removes and returns the first element from this list.
* @return the first element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeFirst() {
final Node<E> f = first;
// 当头部节点为null时,抛出NoSuchElementException
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
当确定头节点不为null后,调用unlinkFirst方法将头节点从链表中移除并返回头节点的item值,
/**
* Unlinks non-null first node f.
*/
private E unlinkFirst(Node<E> f) {
// next指向当前链表第二个节点
final E element = f.item;
final Node<E> next = f.next;
// 头节点的item、next指向null,有助于GC
f.item = null;
f.next = null;
// 第二个节点设定为新的头节点,如果next是null说明移除头节点后链表为空,则last也要指向null
first = next;
if (next == null)
last = null;
else // 如果新头节点不是null,设定其prev为null
next.prev = null;
// 更新大小和版本信息并返回先前头节点的item值
size--;
modCount++;
return element;
}
unlinkLast方法尾部删除节点
removeLast方法删除尾部节点并返回删除的节点item值,
/**
* Removes and returns the last element from this list.
* @return the last element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeLast() {
final Node<E> l = last;
// 如果尾节点是null,说明链表为空,抛出异常;反之,调用方法返回相应的值
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
当确定头节点不为null后,调用unlinkLast方法将头节点从链表中移除并返回头节点的item值,逻辑与unlinkFirst相同。
/**
* Unlinks non-null last node l.
*/
private E unlinkLast(Node<E> l) {
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null;
last = prev;
// 如果prev是null,说明链表被删空,此时first需要指向null
if (prev == null)
first = null;
else // 如果没被删空,则设置prev指向null
prev.next = null;
size--;
modCount++;
return element;
}
unlink方法删除指定节点
unlink方法传入Node对象,在链表中删除该节点,
/**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// 获取该节点的值,及其前后节点
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
// 如果prev为null说明该节点是头节点,则first指向next;反之,next是prev的后一个节点
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
// 如果next为null,表示该节点是尾节点,则last指向prev;反之prev是next的前一个节点
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
// 删除的节点item、prev和next设为null,更新大小和版本并返回值
x.item = null;
size--;
modCount++;
return element;
}
LinkedList对象remove方法调用形式
LinkedList实现了List接口,
调用形式 | 返回值类型 | 说明 |
---|---|---|
remove(Object o) | boolean | 指定值o,删除链表中第一个item等于o的节点。方法调用unlink方法。 |
remove(int index) | E | 删除指定位置的节点,并返回其item值。方法调用unlink方法。 |
LinkedList实现Queue接口和Deque接口后新增的添加元素的方法,
调用形式 | 返回值类型 | 说明 |
---|---|---|
poll() | E | 检索并删除头节点,当链表为空时返回null。方法调用unlinkFirst方法。 |
pollFirst() | E | 检索并删除头节点,当链表为空时返回null。方法调用unlinkFirst方法。 |
pollLast() | E | 检索并删除尾节点,当链表为空时返回null。方法调用unlinkLast方法。 |
removeFirst() | E | 检索并删除头节点,当链表为空时抛出异常。方法调用unlinkFirst方法。 |
removeLast() | E | 检索并删除头节点,当链表为空时抛出异常。方法调用unlinkLast方法。 |
remove() | E | 检索并删除头节点,当链表为空时抛出异常。方法调用removeFirst。 |
removeFirstOccurrence(Object o) | boolean | 删除第一次出现的item等于o的节点,方法调用remove(Object o)方法。 |
removeLastOccurrenceObject o) | boolean | 删除最后一个的item等于o的节点,方法调用unlink方法。 |
迭代器
LinkedList要是想双向迭代访问,所以Iterator接口不能满足需求,因为Iterator只支持从头到尾的访问。Java新增了一个迭代接口,ListIterator,该接口提供了向前和向后的迭代方法,
迭代方向 | 方法 |
---|---|
从头到尾 | hasNext、next、nextIndex |
从尾到头 | hasPrevious、previous、previousIndex |
LinkedList中的内部类ListItr实现了该接口,
private class ListItr implements ListIterator<E> {
//上一次执行 next() 或者 previos() 方法时的节点位置
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
//expectedModCount:期望版本号;modCount:目前最新版本号
private int expectedModCount = modCount;
ListItr(int index) {
// 创建时依据index得到next的位置
next = (index == size) ? null : node(index);
nextIndex = index;
}
......
}
前向迭代
public boolean hasNext() {
return nextIndex < size;
}
public int nextIndex() {
return nextIndex;
}
public E next() {
//检查期望版本号有无发生变化
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
// 此处next是当前节点,lastReturned指向该节点。之后对next进行更新,指向下一节点
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
后向迭代
从尾部到头部的迭代稍微复杂,
public boolean hasPrevious() {
return nextIndex > 0;
}
public int previousIndex() {
return nextIndex - 1;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
// 如果当前节点是null,表示上次迭代的是尾节点,next和lastReturned指向last即可;反之,指向当前节点的前一节点
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
相较于前向迭代的复杂之处在于判断next是否为空。
迭代器删除
调用的是ListIterator的remove方法,
public void remove() {
checkForComodification();
// next()和previous()方法中,lastReturned指向的都是迭代出的值。remove方法就是要删除lastReturned指向的值
// 如果为null,说明之前已经被删除过,不能重复删除,否则会报错
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
// 删除该节点
unlink(lastReturned);
// 之前执行previous方法后,lastReturned和next指向同一个节点对象
// 此时要删除该对象,则next需指向该节点的后一节点。
// nextIndex无需改动,因为previous方法中已经对其-1操作
if (next == lastReturned)
next = lastNext;
// 之前执行next方法后,lastReturned和next指向不同对象
// 此时删除lastReturned不需要对next改动
// 只用对nextIndex减1即可,因为next方法中对该变量+1
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
总结
LinkedList适用于对顺序有要求并按照顺序迭代的场景。