一、双循环链表插入操作处理
双循环链表 中 , 需要对 插入 / 删除 / 遍历 操作 进行特殊处理 , 因为需要调节 前驱指针 和 后继指针 两个指针 ;
如 : 双循环链表 中 , 如果要插入元素 , 将 c 节点 插入到 a 节点 和 b 节点 之间 ,
当前的状态是 a 的后继指针 指向 b , b 的前驱指针指向 a ;
如果要实现插入 c 元素 , 则需要
- 将 a 的 后继指针 指向 c ,
- 将 c 的 前驱指针 指向 a ,
- 将 c 的 后继指针 指向 b ,
- 将 b 的 前驱指针 指向 c ;
插入节点操作 需要执行四个步骤 :
- ① 将 c 的 前驱指针 指向 a
- ② 将 a 的 后继指针 指向 c
- ③ 将 c 的 后继指针 指向 b
- ④ 将 b 的 前驱指针 指向 c
二、双循环链表删除操作处理
下面的链表插入成功 , 顺序为 a , c , b ,
如果要删除双循环链表中的 c 元素 , 只需要将 a 元素的 后继指针 指向 b , 将 b 元素的 前驱指针 指向 a 即可 ;
c 元素没有指针指向后 , 会自动被内存回收 ;
三、LinkedList 双循环链表源码分析
LinkedList 源码地址 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java
1、链表节点
LinkedList 链表是一个 双循环链表 , 下面的 Node 类 , 就是双循环链表的 节点 ;
代码语言:javascript复制 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;
}
}
源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#1021
2、LinkedList 链表中收尾元素指针
在 LinkedList 双循环链表中 , 维护了 首元素节点指针 transient Node<E> first , 尾元素节点指针 transient Node<E> last , 分别指向 首尾元素 ;
代码语言:javascript复制 transient int size = 0;
/**
* 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;
3、链表插入操作
LinkedList 双循环链表 调用 add 方法 添加元素 , 在其中调用了 linkLast 函数 , 将元素插入到了队尾 ;
代码语言:javascript复制 /**
* 将指定的元素追加到此列表的末尾。
*
* <p>这个方法等价于 {@link #addLast}.
*
* @param e 元素添加到此列表
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#354
在 linkLast 函数中 , 创建了新的节点 , 将数据设置到了新节点中 , 最后将新节点设置为 尾部节点 ;
注意 , 设置新的尾部节点时 ,
- 首先 , 保存原来的尾部节点指针 ( 现在不保存 , 之后访问不到了 ) ;
- 然后 , 将新的节点设置为 尾部节点 ;
- 最后 , 将原来的 尾部节点 的后继指针 指向新插入的节点 ;
/**
* 链接作为最后一个元素。
*/
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 ;
}
源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#147
4、链表向指定位置插入操作
调用 LinkedList 的 public void add(int index, E element) 函数 , 可以向指定索引添加元素 ,
如果添加的非末尾元素 , 则调用 linkBefore 函数 向 链表 中插入数据 ;
代码语言:javascript复制 /**
* 将指定元素插入此列表中的指定位置。
* 将当前在该位置的元素(如果有的话)
* 和任何后续元素向右移动(在它们的索引上加1)。
*
* @param index 要插入指定元素的索引
* @param element 要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
// 检查索引合法性
checkPositionIndex(index);
if (index == size)
// 如果是添加到末尾
linkLast(element);
else
// 如果是添加到非末尾的元素
linkBefore(element, node(index));
}
源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#532
void linkBefore(E e, Node<E> succ) 函数 , 是将 E 数据对应的节点插入到 Node<E> succ 数据之前 ;
代码语言:javascript复制 /**
* 在非空节点 succ 前插入元素 e
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 获取 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 ;
}
源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#163
5、获取指定索引的元素
LinkedList 中 , 调用 public E get(int index) 函数 , 获取指定索引的元素 ;
checkElementIndex 函数的作用是 检查该索引是否合法 ;
node 函数就是获取 双循环链表 元素的方法 ;
代码语言:javascript复制 /**
* 返回列表中指定位置的元素。
*
* @param index 要返回的元素的索引
* @return 在此列表中指定位置的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#500
Node<E> node(int index) 函数的核心操作 , 就是执行 index - 1 次 循环 , 找到对应的节点并返回 ;
在执行前 判定 index 靠近 首元素 还是 尾部元素 , 如果 index < (size >> 1) 可以判定为 index 处于前半部分 ;
- 如果 index 靠近 首部元素 , 则正向遍历 ;
- 如果 index 靠近 尾部元素 , 则逆向遍历 ;
/**
* 返回指定元素索引处的(非空)节点。
*/
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;
}
}
源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#596
6、删除指定索引的元素
LinkedList 双循环链表 中 , 调用 public E remove(int index) 函数 , 删除指定索引的元素 ;
删除的核心操作 , 就是 unlink 函数 , 将指定节点从 双循环链表 中脱离 ;
代码语言:javascript复制 /**
* 移除此列表中指定位置的元素。
* 将所有后续元素向左移动(从它们的索引中减去1)。
* 返回从列表中删除的元素。
*
* @param index 要删除的索引
* @return the 元素先前在指定位置
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#551
unlink 函数中 , 先获取要删除节点的 前驱节点 和 后继节点 , 然后 执行下面两个操作 :
- 前驱节点 的 后继指针 指向 后继节点 ;
- 后继节点 的 前驱指针 指向 前驱节点 ;
/**
* Unlinks non-null node x.
*/
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;
}
源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#220