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