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

jdk源码分析之LinkedList

2016-05-23 15:06 651 查看

LinkedList关键属性

size表示当前链表保存了多少数据,first指针指向链表第一个数据,last指针指向链表最后一个数据

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;


LinkedList底层数据结构

Linked是一个双向链表,链表节点的数据如下,有一个数据域和两个指针域

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首尾添加数据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++;
}


先保存旧链表的尾指针为l,然后根据传入的数据e和为指针l构建一个数据节点。

newNode = new Node<>(l, e, null)

将链表的尾节点修改为新创建的节点newNode。如果l==null,表示之前链表里边没有保存数据,现在添加了一个新数据,头指针和尾指针都应该指向这个新添加的节点

last = newNode;
if (l == null)
first = newNode;


如果之前链表里边保存的有数据,l!=null,则修改新节点链接到原链表的尾部

l.next = newNode;

然后修改size和modCount(迭代器并发访问用到的属性)

linkFirst(E e)代码类似,原理一样

在LinkedList的某一节点前插入数据linkBefore

/**
* Inserts element e before non-null Node succ.
*/
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++;
}


在节点succ前插入数据 e,则succ是e的后继节点,e的前序就是succ的前序,因此根据e、前序、后继构造一个新的节点newNode = new Node<>(pred, e, succ),修改succ的前序节点为新节点newNode,succ.prev = newNode,然后再将断开的链表链接起来

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的话表示原链表只有一个节点,现在还把唯一的节点删除了,因此first头指针和last尾指针都应该为null。next不为null的话表示删除头结点后链表还有数据,修改next的prev指针为null即可(pre指针为null表示此接节点为队首数据,next指针为null表示队尾数据)

unlinkLast(Node l) 代码和原理类似

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指针指向后继节点,待删除节点的prev指针域置null,这样待删除节点的前序节点指针已经断开重连了,然后处理后继节点指针的断开重连

判断后继节点是否为null,是null的话表示待删除节点就是队尾,修改队尾指针last为待删除节点的前序

后继节点不为null,后继节点的prev指针指向待删除节点的前序节点,待删除节点的next指针置null,这样待删除节点的后继结点的指针也已经断开重连了

然后把待删除节点的数据域置null,修改size和modCount,返回删除节点的数据

LinkedList的随机访问node(int index)

/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(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;
}
}


链表插入删除添加数据比较方便,直接修改指针的断连即可。但是链表并不能够像数组那样随机访问,只能进行遍历。LinkedList是一个双向链表,可以对遍历做一个小优化,只遍历半个链表即可。

如果位置在链表前半部分index < (size >> 1),正向遍历查找for (int i = 0; i < index; i++)

否则逆向遍历查找for (int i = size - 1; i > index; i–)

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;
}


可见插入位置区间为【0,size】,可以队首插入,可以队尾插入,中间任意位置当然也可以

然后把集合对象转化为数组,数组长度为0表示集合没有数据,直接返回false表示插入失败

构建插入节点的前序和后继,如果插入位置index==size的话表示队尾插入数据,因此后续为null,前序为队尾last

Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
}


不是在队尾插入集合的话需要根据插入位置index找到后继节点,找到了后继也就找到了前序

else {
succ = node(index);
pred = succ.prev;
}


Node node(int index)方法用于返回特定位置的节点,这样插入点的前序和后继都找到了,开始循环把集合的数据添加到链表的特定位置

for (Object o : a) {
E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}


通过new Node<>(pred, e, null)创建新节点,前序指针在创建的时候在构造函数里边已经指明。

pred == null表示是在队首添加数据,需要修改队首指针first

否则把前序节点的后继指针设置为newNode

再把前序节点设为newNode,接着下一次循环

这样在循环结束后,只有最后一个添加的节点的后继指针没有处理

if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}


如果succ后继为null,修改队尾指针为last为最后一个添加的节点pred

否则设置最有一个添加的节点的后继指针为succ,succ的前序指针指向最后一个添加的节点pred

最后修改size和modCount,返回true表示添加成功

LinkedList的双端队列特性

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable


LinkedList实现了Deque接口,因此可以吧LinkedList当做一个双端队列属性,比如peek和poll,均是通过链表的基本操作实现相应的功能,其他的方法类似,不再一一列举。

public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jdk linkedlist 源码