前言:
1、LinkedList,双向链表,底层数据结构是链表
2、不是线程安全的
3、查询慢,需要遍历链表,添加和删除节点很快,只需移动节点就行
4、既可以当队列,也可以当链表
一些重要的常量和变量
/**
* 链表大小
*/
transient int size = 0;
/**
* 头节点.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 尾节点.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/**
* 静态节点类,双向链表所以有前后指针,LinkedList通过first和last引用分别指向链表的第一个和最后一 个元素。
* 注意这里没有所谓的哑元,当链表为空的时候first和last都指向null。
*/
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简单示意图:(图来自https://github.com/CarpenterLee/JCFInternals/blob/master/markdown/3-LinkedList.md)
一些重要的方法:
-
add()方法,添加新的节点到链表尾部
/**
* 添加新的节点到链表尾部
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 具体实现类
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//如果尾节点为null,则链表为空,将新节点设为头节点
if (l == null)
first = newNode;
//不然就将新节点设为原先尾节点的后面
else
l.next = newNode;
size++;
modCount++;
}
/**
* 在要求的 index 插入元素
*/
public void add(int index, E element) {
checkPositionIndex(index);
// 如果要插入的位置是末尾的位置
if (index == size)
linkLast(element);
// 其他位置
else
linkBefore(element, node(index));
}
/**
* 返回该索引的节点
*/
Node<E> node(int index) {
// 因为链表双向的,可以从开始往后找,也可以从结尾往前找,
// 具体朝那个方向找取决于条件index < (size >> 1),也即是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;
}
}
/**
* 插入元素
*/
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;
// 头结点为null
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
添加节点示意图:
-
删除节点
/**
* 根据 index 删除节点
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
// 检查该 index 是否存在,不存在抛出异常
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 判断index是否在链表中
*/
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
/**
* 删除节点
*/
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;
// 修改 pre 指针指向, 处理头指针为null的情况
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
// 修改 next 指针指向,特殊处理尾指针为null的情况
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
// GC
x.item = null;
size--;
modCount++;
return element;
}
/**
* 根据值删除节点
*/
public boolean remove(Object o) {
if (o == null) {
// 遍历节点信息,找到节点value为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;
}
remove()方法示例图:
3、 set()
/**
* 替换该index的值
*/
public E set(int index, E element) {
// 检查是否存在该索引
checkElementIndex(index);
// 找到该节点
Node<E> x = node(index);
// 值替换
E oldVal = x.item;
x.item = element;
return oldVal;
}
参考:
https://github.com/CarpenterLee/JCFInternals/blob/master/markdown/8-PriorityQueue.md 大佬的集合源码分析,写的很nice,图都来自这上面的。