Java源码篇之容器类——LinkedList
一、前言
对于经常在开发中使用到的LinkedList,一直以来只知道底层是链表实现的,但是很好奇具体实现,以此为目的简单阅读一下它的源码,做个记录,jdk1.8版本。
二、LinkedList的类关系
通过查看LinkedList的类关系图,可以看到实现了
Cloneble接口,支持被克隆;
Serializable接口,支持序列化与反序列化;
Deque接口,支持两端元素插入和移除的线性集合。 名称deque是“双端队列”的缩写。
三、 LinkedList的源码
1、类的属性
/**
* 链表元素个数
*/
transient int size = 0;
/**
* 指向第一个节点的指针
*/
transient Node<E> first;
/**
* 指向最后一个节点的指针
*/
transient Node<E> last;
2、add()方法
// 尾插法添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
// 尾插法
void linkLast(E e) {
// 把链表中最后一个元素(未添加元素时)赋值给l
final Node<E> l = last;
// 创建新的节点Node,并把元素e赋值给Node,前驱指向链表中最后一个元素(未添加元素时)①
final Node<E> newNode = new Node<>(l, e, null);
// 每次新创建的节点都是最后一个节点,所以此处是尾插
last = newNode;
if (l == null)
// 添加第一个元素的时候,first和last均指向此元素
first = newNode;
else
// 将链表中最后一个元素(未添加元素时)的后继指向新添加的节点②
// 结合①②可以看出LinkedList是双向链表实现的
l.next = newNode;
// 元素个数加1
size++;
// 链表修改次数加1
modCount++;
}
// 指定位置添加元素
public void add(int index, E element) {
// 校验合法性
checkPositionIndex(index);
if (index == size)
// 如果指定的索引等于链表元素个数
// 例:链表中有一个元素,索引是从0开始的,0表示第一个位置的元素,1表示第二个位置的元素,所以此处调用尾插添加元素
linkLast(element);
else
linkBefore(element, node(index));
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
// 指定的索引需要非负并且不大于链表的大小
return index >= 0 && index <= size;
}
// 在非空元素succ(目标索引位置上的元素)之前插入元素e
void linkBefore(E e, Node<E> succ) {
// succ前一个节点
final Node<E> pred = succ.prev;
// 在pred和succ之间添加元素e
final Node<E> newNode = new Node<>(pred, e, succ);
// succ的前驱指向新创建的元素
succ.prev = newNode;
if (pred == null)
// 说明此索引之前无元素,first指向新创建的节点
first = newNode;
else
// succ前一节点的后继指向新创建的节点,形成双链
pred.next = newNode;
size++;
modCount++;
}
// 内部静态类
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;
}
}
3、get()方法
public E get(int index) {
// 校验索引合法性
checkElementIndex(index);
// 返回索引节点的元素值
return node(index).item;
}
4、remove()方法
// 根据索引删除
public E remove(int index) {
// 校验索引合法性
checkElementIndex(index);
return unlink(node(index));
}
// 此方法才是真正删除节点的方法
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) {
// x节点的前驱为null,此时x是链表中第一个元素节点
// 头指针指向下一个节点就表示删除此节点
first = next;
} else {
// x的前驱不为null,x元素的前一节点的后继指向x的后一个元素节点
prev.next = next;
// x元素的前驱置为null
// x元素节点的后继还存在,此时还未完全删除成功
x.prev = null;
}
// 处理该节点的后继指针
// 指针指向绕过x,表示x元素节点从链表中删除
if (next == null) {
// x元素节点的后继为null,表示此为链中最后一个元素节点
// 尾结点指向此节点的前驱
last = prev;
} else {
// x元素节点的后继不为null
// 修改该节点的后一个节点的前驱指向,此时已经没有指针指向x元素节点
next.prev = prev;
// x元素节点的后继指针置空,此时该节点完全孤立,下一次GC发生时会回收此节点
x.next = null;
}
x.item = null;
size--;
modCount++;
// 返回此节点元素值
return element;
}
// 根据对象删除
public boolean remove(Object o) {
// 要删除的对象分为null和非null两种情况处理,处理逻辑一致
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;
}
5、set()方法
// 将索引位置的元素替换成目前值element
public E set(int index, E element) {
// 校验索引合法性
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
// 替换目标值
x.item = element;
// 返回被替换的值
return oldVal;
}
四、总结
- 通过阅读源码可以看出,LinkedList是双向链表实现的;
- LinkedList是线程非安全的;
- add()和remove()删除方法只需要修改指针即可,增删类比数组很高效;