文章目录
LinkedList
Author : lss 路漫漫其修远兮,不止于代码
简介
LinkedList 是是由双线链表构成 ( 这里是围绕 JDK1.8开展 )。同时实现了 List 和 Deque 接口, 也就是说它既是一个顺序容器也是一个队列(queue), 与此同时也可以看成是一个栈 (Stack)。 虽然说它也可以当作队列 和 栈,但是并不是我们首先要考虑从的。在队列和栈的结构上应该优先考虑 ArrayDeque 。它有着比 LinkedList 有好的性能。 这里不做详细比较。
本次只对 构造方法,删除,插入,获取做详细介绍,关于 LinkedList 的 队列(queue) 和 栈 (Stack) 下期再做解析。
构造方法
LinkedList ()
// 无参构造中没有做任何事情
public LinkedList() {
}
再看这段源码的时候,请分别一个东西 前指针(前驱节点),后指针 (后驱节点) 头节点 , 尾节点。
LinkedList(Collection<? extends E> c)
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
public boolean addAll(int index, Collection<? extends E> c) {
// 校验下标
checkPositionIndex(index);
// 将参数集合转化为数组
Object[] a = c.toArray();
// 获取数组长度
int numNew = a.length;
if (numNew == 0)
return false;
// pred 为 succ 的前驱节点
Node<E> pred, succ;
// 没有数据的情况下
if (index == size) {
succ = null;
// pred 为尾节点
pred = last;
} else {
// 通过下标寻找节点 赋值给 succ
succ = node(index);
// 将 succ的前驱节点改为 pred
pred = succ.prev;
}
// 遍历数组
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
// 将值 包装为 Node
Node<E> newNode = new Node<>(pred, e, null);
// == null 代表为 first 节点 (头节点)
if (pred == null)
// 头节点为当前插入的值
first = newNode;
else
// 不是 first 头节点 将 尾指针指向当前插入的值
pred.next = newNode;
// 前驱为 当前插入的值
pred = newNode;
}
// 因succ是pred的后指针 如果 succ为 null 则代表是尾节点
if (succ == null) {
// 尾节点 指向 pred
last = pred;
} else {
// 不是尾节点 只需要保证 pred 是 succ 的前指针即可
pred.next = succ;
succ.prev = pred;
}
// size = 当前集合长度 + 新添加集合长度
size += numNew;
// 添加记录
modCount++;
return true;
}
添加方法
add(E e)
void linkLast(E e) {
// 将 l 设置为 尾节点
final Node<E> l = last;
// 将插入的值 包装为 Node
final Node<E> newNode = new Node<>(l, e, null);
// 将尾节点 修改为 当前插入的节点
last = newNode;
// 为 null 表示还没有数据插入
if (l == null)
//将头节点改为 当前插入的节点
first = newNode;
else
//有数据存在 将之前的尾节点的后指针改为 当前节点。
l.next = newNode;
// 长度 + 1
size++;
modCount++;
}
add(int index, E element)
友情提示 : 这副图着实是有点太丑了, 不过将就的看一下吧 。 这是根据下标插入元素时, 各个节点指针的改动。请先看这块指针指向的改变。在往下看源码,不然你可能会被绕晕在源码里。
public void add(int index, E element) {
// 校验下标
checkPositionIndex(index);
// 下标 == 集合长度 代表从尾节点正常插入 也就是继续顺序插入
if (index == size)
// 这个方法在上面写过, 不在展开介绍
linkLast(element);
else
// 在中间进行插入
// node( index ) 是根据下标找对应的节点 寻找过程在后续
linkBefore(element, node(index));
}
/**
* succ 为根据下标 取出的节点。 这里是将他的前驱节点 换成 要插入的节点,将要插入的节点前驱节点
* 设为 succ 的前驱节点, 后驱节点为 succ。 将 succ的前驱节点的后驱节点设置为 当前插入的节点
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null; assert 是一个断言操作。 关于断言主要用户测试,这里不做详细介绍。
// 拿到该节点的前驱节点
final Node<E> pred = succ.prev;
// 将插入的值包装成 Node
final Node<E> newNode = new Node<>(pred, e, succ);
// 将当前节点的 前驱节点 改为 当前插入的节点
succ.prev = newNode;
// 若最初拿到的 前驱节点去 null 则代表他为 头节点
if (pred == null)
first = newNode;
else
// 将最初节点的 后驱节点改为当前插入的节点。
pred.next = newNode;
size++;
modCount++;
}
// 这块看下 node(inedx) 的源码 他是如何快速的找到该 Node 节点
Node<E> node(int index) {
// assert isElementIndex(index);
// 如果这个下标的位置在前半部分
if (index < (size >> 1)) {
// 从 first 头节点开始去寻找
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;
}
}
// 采用双向链表后就解决了以往单链表查询效率慢的因素。 这里他会根据下标的位置,首先将一部分数据过滤,在剩余一部分中去查找。
删除方法
remove(int index)
public E remove(int index) {
checkElementIndex(index);
// node(index) 上面已经解释过了这里不在多说
return unlink(node(index));
}
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;
// 若前驱节点为 null 代表该节点为头节点
if (prev == null) {
// 头节点设置为 当前节点的后驱节点
first = next;
} else {
// 将当前节点前驱节点的后驱节点 设置为 当前节点的后驱节点
prev.next = next;
// 将当前节点的前驱节点 置 null
x.prev = null;
}
// 若当前节点的后驱节点 为 null 代表为尾节点
if (next == null) {
//将当前节点的前驱节点设置为 尾节点
last = prev;
} else {
// 将当前节点的后驱节点的前驱节点 设置为 当前节点前驱节点
next.prev = prev;
// 将当前节点的后驱节点 置 null
x.next = null;
}
// 将当前节点 元素 置null 上期已经说过 这里是为了明确告诉 GC
x.item = null;
// 改变集合长度
size--;
modCount++;
return element;
}
remove(Object o)
public boolean remove(Object o) {
if (o == null) {
// 删除集合中的 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;
}
获取方法 get
public E get(int index) {
// 这两个方法以上都有详细解释
checkElementIndex(index);
return node(index).item;
}