LinkedList源码解析
Posted firepation
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LinkedList源码解析相关的知识,希望对你有一定的参考价值。
1. 底层数据结构
在 LinkedList 中,定义了一个内部类 Node
来保存每个节点的信息。在这个内部类中,有一个 E 类型的变量,用于存储该节点的值;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;
}
}
在 LinkedList 这个类中,定义了 first 和 last 两个变量来指向链表的头结点和尾节点。在源码中,作者还给了注释,由此可以看出,在存储过程中,第一个节点的 prev 是为 null 的,而第一个节点是有存储值的。最后一个节点也是有存储值的,但是最后一个节点的 next 属性是为 null 的。
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
2. 常用 API
2.1 添加元素
链表中的 add 方法是将指定元素添加到链表的末尾,主要是调用了 linkLast 方法。
public boolean add(E e) {
linkLast(e);
return true;
}
linkLast 方法是添加元素的关键,它首先构造一个节点,节点的前一个元素为 last 变量,后一个节点为 null。接着判断头结点是否为空,如果为空,表示该链表中还没有元素,则将头结点指向该节点。此时,头结点的 prev 变量指向的是 last,而 last 为 null,符合上面注释定义的规则。
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++;
}
2.2 删除元素
remove 函数主要是遍历整个链表,当找到要删除的节点时,使用 unlink 方法删除这个节点。
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;
}
接下来我们就来看一下 unlink 函数的实现。
该函数首先判断 prev 变量是否为 null,如果为 null,则说明要删除的节点为头结点,删除头结点只需要 first 指向下一个节点即可。如果前一个节点不为 null,则将前一个节点的 next 指向 next 节点。
接着判断要 next 变量是否为 null,如果为 null,则说明该节点是最后一个节点,那么只要将 last 指向 prev 就可以删除和最后一个节点之间的联系了。如果不为 null,则将下一个节点的 prev 指向要删除节点的前一个节点,即 prev 变量。
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++;
return element;
}
2.3 查找元素
查找元素是通过 get 这个方法来实现的,在方法内部,首先利用 checkElementIndex
方法检查 index 是否在指定范围内,如果不再指定范围内则抛出 IndexOutOfBoundsException 的异常。接着调用 node 方法查找指定元素。
在 node 方法内部,通过判断需要查找元素是在链表的前半段还是后半段,然后通过 prev 或者 next 遍历来查找,这是提升查找速度的一个小细节。
Node<E> node(int index) {
// assert isElementIndex(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;
}
}
2.4 迭代器
集合框架实现迭代器的原理差不多,大致是这个模式,根据底层数据结构的不同实现 Itr 类中的 hasNext、remove、next 方法。
public Iterator<E> iterator(){
return new Itr();
}
private class Itr implements Iterator(){
public boolean hasNext(){
}
public void remove(){
}
public E next(){
}
}
以上是关于LinkedList源码解析的主要内容,如果未能解决你的问题,请参考以下文章