一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。
LinkedList的基本介绍
LinkedList集合是我们实际工作中使用的最多的集合之一,其同时实现了List和Queue接口,也就是说它同时具备List和Queue两者的特性;
LinkedList集合的主要结构是双向链表,双向链表中的节点不要求有连续的内存存储地址;说起链表,我们大家应该都很熟悉了,其基本结构如下图所示:
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;
}
}
item表示当前元素,next指向的是下一个节点,prev指向的是上一个节点;
LinkedList使用first表示头节点,使用last表示尾结点,使用size表示当前双向链表的长度;
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
}
LinkedList集合的添加操作
LinkedList集合的添加操作对外暴露了add(E),addFirst(E),addLast(E),add(int,E),push(E)等等多个方法,但其底层调用的方法基本三个方法可以概括:linkFirst(E),linkLast(E),linkBefore(E e, Node succ),下面我们分别从这三个方法层面进行剖析;
linkFirst(E)
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
我们解析下这段代码:
- first代表的是当前LinkedList的第一个元素,因为我们这个方法是linkFirst,所以见名知义,我们要把这个元素放到LinkedList的第一个节点,所以我们先拿到第一个节点;当然这个节点也可能是null;
- 将当前元素构建一个Node节点,这个元素要是未来新链表的头部,所以它的上一个节点为null,下一个节点就是我们刚才找到的头部节点first,也就是f
- 目前新Node已经融合进链表了,所以新链表的头部也就变成了新Node
- 如果之前链表的头部,也就是f是null的话,说明之前是个空链表,换句话说当前新链表也就只有e一个节点;所以说last也应该是e,也就是newNode
- 如果之前f不是null的话,说明之前链表是有元素的,但是因为f是first节点,它的上一个元素肯定是null,又因为现在的领头人变成了newNode,所以原来的领头人first就有了新的领头人也就是newNode
linkLast(E)
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++;
}
我们解析下这段代码:
- linkLast见名知义就是要把这个元素放到尾部,最后一个节点;所以我们先获取当前节点的last元素
- 同样我们构建一个newNode,这个时候e的上一个节点就是l,下一个节点就是null
- 因为我们这个newNode是要放到尾部的,所以我们将last赋值为newNode
- 同样,如果l为空的话说明之前的list是不含有任何元素的,所以这个时候集合的first,last同时为newNode
- 如果l不为null,那么l的下一个节点就是新的尾部节点 newNode;
linkBefore(E, Node)
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
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++;
}
linkBefore意思是在某个指定的节点之前添加;我们解析下这段代码:
- 首先我们获取指定节点succ的前一个节点pred
- 我们创建一个新的节点,这个节点要在pred和succ之间;
- 修改succ的之前的节点引用为当前节点newNode
- 如果pred为null的话,说明之前的first为succ,这个时候first就是newNode
- 如果pred不为null的话,修改pred的下一个节点为newNode
我们解析了上面三个方法,说是解析,其实也就是翻一下代码啦,作者的整个思路是比较清晰的,我们需要清楚的是对于LinkedList,是不可能存在 first == null && last != null 和 first != null && last == null 这两种情况的;
LinkedList集合的移除操作
LinkedList对外暴露了许多删除的方法,比如说removeLast(),removeFirst(),remove(Object)等等方法,但其实其底层比较关键的方法只有三个,unlinkFirst(Node),unlinkLast(Node),unlink(Node),下面我们分别解析下
unlinkFirst(Node)
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
unlinkFirst见名知义就是删除第一个元素节点,如果删除了第一个元素节点,那么第二个元素节点就会变成新的头节点,下面我们解析下这个方法;
- 将f的item赋值给element,element实际是没有什么用的,只是返回使用;
- 获取f的next节点,这个就是未来的头节点
- 将f的item置为null,是为了方便gc释放element
- 将f的next置为null,是为了方便gc释放f自身
- next就是新的头节点
- 如果next是null,说明之前只有一个元素,现在一个也没有了,所以last也应为null
- next不为null,成为了新的头节点,所以它的前一个元素应该是null
unlinkLast(Node)
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
unlinkLast就是为了释放最后一个节点,这个时候unlinkLast的prev就会变成头节点了,这个方法和上面的方法思想都差不多,我们就不解析了,相信聪明的你已经掌握了。
unlink(Node)
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.next的prev变成了x.prev,x.prev的next就变成了x.next;这个思想也比较简单;
关于LinkedList我们今天就先说到这里啦,今天周五,大家早点休息,辛苦了一周,希望大家快快乐乐的度过周末,今天我们主要简单介绍了下LinkedList的增删,下一次我们介绍一下查询以及和ArrayList的对比介绍