jdk源码分析之LinkedList
LinkedList关键属性:
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;
size属性表示当前链表有多少个元素,first指针指向链表的第一个元素,last指针指向链表的第二个元素。
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;
}
}
可知LinkedList底层是一个双向链表,一个头指针,一个尾指针,还有一个数据域。
LinkedList首位添加数据linkFirst(E e)和linkLast(E e)
/**
* Links e as last element.
*/
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++;
}
先保存就链表的尾指针,然后根据传入的e 和 l构建一个数据节点。如果l == null,则表示先前链表为空,并将刚构建的节点放入链表中,头指针和尾指针都指向该新建的节点。如果l != null,则表示先前链表中有数据,并将新构建的节点放到链表尾部。linkFirst(E e)源代码和它类似,原理相同。
LinkedList删除头结点unlinkFirst()和尾节点unlinkLast()
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
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;
}
先保存头结点的后继节点,然后头结点指针域和数据域置null,加速内存回收速度,然后判断next是否为null,如果为null,证明现在把链表中唯一的节点删除了;若不为null,表示删除节点后链表还有数据,这时修改next的pre指针为null即可。unlinkLast()原理类似。
LinkedList删除某一节点unlink()
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;
}
本质还是对指针的操作,先取得删除节点的数据域,后继和前继,然后进行断链操作。
前序为null,表示待删除节点是头结点,修改first指针为待删除节点的后继节点。
前序节点不为null就把前序节点的next指针指向后继节点,待删除节点的pre为null.
判断后继节点是否为null,是null的话表示待删除节点就是队尾节点,然后修改队尾指针last为待删除节点的前驱。
后继节点不为null,则后继节点的prev指针指向待删除节点的前序节点,待删除节点的next指针指向null.
最后把待删除节点的数据域置null.
LinkedList的某一个位置添加一个集合addAll
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
首先检查插入位置是否合法,可以队首插入,也可以队尾插入,中间任意位置当然也可以,然后把集合对象转化为数组,构建节点的前驱和后继。如果插入位置index==size的话表示队尾插入数据,因此后续为null,前序为队尾last不是在队尾插入集合的话需要根据插入位置index找到后继节点,找到了后继也就找到了前序,通过new Node<>(pred, e, null)创建新节点,前序指针在创建的时候在构造函数里边已经指明。 pred == null表示是在队首添加数据,需要修改队首指针first 否则把前序节点的后继指针设置为newNode
再把前序节点设为newNode,接着下一次循环 这样在循环结束后,只有最后一个添加的节点的后继指针没有处理,如果succ后继为null,修改队尾指针为last为最后一个添加的节点pred 否则设置最有一个添加的节点的后继指针为succ,succ的前序指针指向最后一个添加的节点pred 最后修改size和modCount,返回true表示添加成功。