您的位置:首页 > 编程语言 > Java开发

JDK源码走读之LinkedList

2017-10-24 17:00 363 查看
LinkedList本质是一个双端链表,双端链表区别于双向链表,双向链表之链表的首位相连,而双端链表则分别持有链表头尾两个节点,访问时既可以从头开始,也可以从尾部开始。

链表结构



定义

LinkedList实现了List、Dequet接口使它兼具二者的特性,既支持集合的添加删除操作,又支持队列的出队入队操作

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; // 队列尾

......

private static class Node<E> {
E item;

4000
Node<E> next;
Node<E> prev;

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
}


核心方法

ArraList提供了对队列操作的几个核心方法,包括把数据添加到队列头部、中间、尾部,以及对应的移除方法。

其他List类型的add、get、remove等相关API实现,以及队列类型的push、pop、peek等相关的API实现,都是对这些核心方法的调用不再赘述。

// 把元素添加到队列头,并把添加入节点和first节点连接
// 新节点后继指向first节点,first节点前继指向新节点
private void linkFirst(E e) {
final Node<E> f = first;
// 创建一个新的节点,prev为null,next为当前头结点
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
// 如果当前头节点为null,说明队列为空,
// 新加入的节点既是头节点又是尾节点
last = newNode;
else
// 如果当前头结点不为空,
// 则把头结点的prev指向新节点
f.prev = newNode;
size++;
modCount++;
}

// 把元素添加到尾节点,并把last节点和新节点连接
// last节点的后继指向新节点,新节点的前继指向last
void linkLast(E e) {
final Node<E> l = last;
// 创建一个新的节点,prev为尾节点,next为null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
// 如果尾节点为空,说明队列为空
// 新加入的节点既是头节点又是尾节点
first = newNode;
else
// 如果当前头结点不为空,
// 则把尾节点的next指向新节点
l.next = newNode;
size++;
modCount++;
}

// 在指定节点E的前边插入新节点
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
// 创建节点,prev指向指定节点E的prev,next指向执行节点
final Node<E> newNode = new Node<>(pred, e, succ);
// 把指定节点的prev指向新节点
succ.prev = newNode;
if (pred == null)
// 如果指定节点E的前继节点为null,说明指定节点为头节点
// 新插入的节点在头结点之前,成为新的头结点
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}

// 移除头结点,头结点的next节点晋升为新的头结点
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
// 释放引用,以便GC回收引用的对象
f.item = null;
f.next = null;
// 把头结点的next设为头结点
first = next;
if (next == null)
// 如果头结点的next为null,说明队列中只有一个元素,
// 释放头结点之后队列为空,头尾节点都为null
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}

// 移除尾节点
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
// 释放引用,以便GC回收引用的对象
l.item = null;
l.prev = null;
last = prev;
if (prev == null)
// 如果前继节点为null,说明队列中只有一个元素,
// 释放尾节点之后队列为空,头尾节点都为null
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}

// 释放任意节点,并使节点的pre、next互为前继、后继节点
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 {
// 前继节点的next指向后继节点
prev.next = next;
x.prev = null;
}

if (next == null) {
// 如果当前节点的后继节点为空,则说明是尾节点
// 删除当前节点之后,其前继节点作为尾节点
last = prev;
} else {
// 删除当前节点之后,后继节点的prev指向前继节点
next.prev = prev;
x.next = null;
}
// 释放节点引用的对象,以便GC回收
x.item = null;
size--;
modCount++;
return element;
}


查找方法

获取队列某个位置的元素,去元素时首先根据给定的偏移量判断元素从距离头部和尾部哪一端更近,再从近端开始遍历查找。 这是双端队列优势的体现。如一个长度为10000的队列,要获取第9999个元素,如果采用单端队列存储需要从第一个元素开始遍历到9999个元素;使用双端队列只需要从尾部开始往前遍历取第二个。

Node<E> node(int 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;
}
}


清空队列

// 清空队列
public void clear() {
// 遍历队列,移除队列中的所有元素
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
// 释放队列中的元素,以便GC回收
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}


总结

LinkedList有较高的插入、删除效率和不错的查询效率,对队列的头尾进行操作时间复杂度为O(1),在指定位置操作时间复杂度为O(n)。

LinkedList不是线程线程安全的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: