1、LinkedList 简介
LinkedList继承于AbstractSequentialList的双向链表,实现List接口,因此也可以对其进行队列操作,它也实现了Deque接口,所以LinkedList也可当做双端队列使用,还有LinkedList是非同步的。
由于LinkedList的底层是双向链表,因此其顺序访问的效率非常高,而随机访问的效率就比较低了,因为通过索引去访问的时候,首先会比较索引值和链表长度的1/2,若前者大,则从链表尾开始寻找,否则从链表头开始寻找,这样就把双向链表与索引值联系起来了。
2、成员变量
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable,
java.io.Serializable{
private static final long serialVersionUID = 876323262645176354L;
// 链表当前节点数
transient int size = 0;
// 头节点
transient Node<E> first;
// 尾节点
transient Node<E> last;
}
3、构造方法
public LinkedList() {}
// 其实和上面的一样,只是多了个 addAll(c)
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
4、核心方法
关键是 Node 节点的结构:双向链表。因此添加删除元素都是遍历链表进行。如果是表头|表尾则可以直接通过first|last进行操作。中间的则需要遍历。遍历的优化:index在前半部分,则从头开始遍历查找;index在后半部分,则从尾开始遍历查找。特别注意,null时,更新first或者last
4.1、Node 内部类
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;
}
}
4.2、link 方法将节点添加到链表
(1)linkLast(E e)
将元素 e 连接到链表尾部
注意,原始表可能是空表,这个时候需要注意 first 的变化
void linkLast(E e) {
final Node<E> l = last;
// 构造Node节点,前驱是last,后继是null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null) //处理空表情况
first = newNode;
else
l.next = newNode; // 将新节点连接到last尾部
size++; //更新size
modCount++; //更新modCount
}
(2)linkFirst(E e)
将值 e 插入到链表头部
注意:原链表可能是空表,这个时候需要注意 last 的变化
private void linkFirst(E e) {
final Node<E> f = first;
// 构造节点:前驱null,后继first
final Node<E> newNode = new Node<>(null, e, f);
first = newNode; //更新first节点
if (f == null) //处理空表情况
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
(3)linkBefore(E e, Node<E> succ)
将值 e 插入到节点succ的前面。
注意 succ 可能是头节点,这个时候需要注意 first 的变化
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
// 构造节点:前驱是succ的前驱,后继是succ
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null) //处理succ是链表头节点的情况
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
4.3、unlink 方法将节点从链表中删除
unlink(Node<E> x)
将节点 x 从链表中断开
注意:prev=null 和 next=null
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
// 取得节点x的前驱和后继
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) { //如果前驱为null(x节点是first节点),则需要更新first(即将x从链表中断开)
first = next;
} else { //将x的前驱断开
prev.next = next;
x.prev = null;
}
if (next == null) { //如果后继为null(x节点是last节点),则需要更新last节点(即将x节点从链表中断开)
last = prev;
} else { //将x的后继断开
next.prev = prev;
x.next = null;
}
x.item = null; //x置空,方便GC
// 更新统计信息
size--;
modCount++;
return element;
}
unlinkFirst(Node<E> f)
将首节点first从链表中断开
注意:删除之后,链表为空,需要更新last节点
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; //更新first(即将f节点从链表中断开)
if (next == null) //处理删除之后,链表为空的情况
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
unlinkLast(Node<E> l)
将尾节点从链表中断开
注意删除之后链表为空,需要更新first节点
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null) // 删除之后链表为空,更新first节点
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
5、常用方法
5.1、add 方法
主要是调用 linkxxx 方法
(1)add(E e)
实际上就是调用的 linkLast(e)
public boolean add(E e) {
linkLast(e);
return true;
}
(2)add(int index, E element)
分情况调用的:linkLast(element) 或者 linkBefore(element, node(index))
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
checkPositionIndex(int index)
检查 index 的合法性
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
isPositionIndex(int index)
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
(3)addFirst(E e)
就是调用的 linkFirst(e)
public void addFirst(E e) {
linkFirst(e);
}
(4)addLast(E e)
实际上就是调用的 linkLast(e)
public void addLast(E e) {
linkLast(e);
}
(5)addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
(6)addAll(int index, Collection<? extends E> c)
将集合c中的元素插入到 index 的前面,注意 index 从0开始
注意:原链表为空表时,index 后面就没有需要处理的数据了
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); //检查index的合法性
Object[] a = c.toArray(); //将集合c中元素输出到一个数组a
int numNew = a.length;
// 如果集合c中没有元素,则返回false
if (numNew == 0)
return false;
Node<E> pred, succ;
// 简单情况:当插入位置是index=size时,插到链表的last的后面
if (index == size) {
succ = null;
pred = last;
} else { //否则
succ = node(index); //找到index处的节点
pred = succ.prev;
}
// 遍历数组a,进行插入(尾插法)
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
// 构造节点:前驱pred,后继为null
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 { //否则,处理原链表index后面的元素
pred.next = succ; // 插入元素的最后一个元素,它的next指向index处的succ
succ.prev = pred;
}
// 更新统计信息
size += numNew;
modCount++;
return true;
}
node(int index)
返回从first开始的第 index个节点(0,1,2,3,4...);注意是从0开始
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;
}
}
5.2、offer 方法
就是调用 addxxx() 方法
都是调用add对应的方法:offerxxx() ---> addxxx()
offer(E e)、offerLast(E e)
添加到尾部
public boolean offer(E e) {
return add(e);
}
public boolean offerLast(E e) {
addLast(e);
return true;
}
offerFirst(E e) 、push(E e)
添加到头部
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
public void push(E e) {
addFirst(e);
}
5.3、删除元素
就是调用 unlinkxxx() 方法
(1)remove()
public E remove() {
return removeFirst();
}
(2)removeFirst()
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
(3)removeLast()
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
(4)remove(int index)
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
(5)remove(Object o)
public boolean remove(Object o) {
if (o == 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;
}
(6)removeFirstOccurrence(Object o)
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
(7)removeLastOccurrence(Object o)
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
5.4、poll 操作
就是调用 unlinkxxx() 方法
poll()、pollFirst()、pop()
删除头节点,实际上就是调用了 unlinkFirst(f)
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pop() {
return removeFirst();
}
pollLast()
删除为节点,实际上就是调用了 unlinkLast(l)
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
5.5、访问元素
和 ArrayList 不同。ArrayList 的底层是数组实现的,因此获取元素就是返回 object[index] 如此简单就行了。但是 LinkedList 的底层是 链表,当需要取index处的元素时,需要遍历整个链表。由于是双向链表,因此可以根据index在 前/后 半部分,选择是从头开始,还是从尾开始遍历。
get(int index)
注意:index从0开始,实际上是调用的 node(index)
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
getFirst()
取 链表头节点的值
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
getLast()
取 链表尾节点的值
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
peek() 、peekFirst()
访问头节点
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
peekLast()
访问尾节点
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
6、其他方法
listIterator(int index)、size()、isEmpty()等简单方法就不做介绍了
set(int index, E element)
就是调用的 node()
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
contains(Object o)
实际上就是调用的 indexOf(o)
public boolean contains(Object o) {
return indexOf(o) != -1;
}
indexOf(Object o)
返回 o 的第一次出现的下标(从0开始);null也行
public int indexOf(Object o) {
int index = 0;
if (o == null) { //如果o是null,则找到第一个null的下表
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
lastIndexOf(Object o)
返回 o 最后一次出现的下标(从0开始,到size-1)
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
element()
public E element() {
return getFirst();
}
7、内部类
不做分析
8、总结
整体分析下来,其实LinkedList还是比较简单的,上面对一些重要的相关源码进行了分析,主要重点如下:
#1.LinkedList底层数据结构为双向链表,非同步。
#2.LinkedList允许null值。
#3.由于双向链表,顺序访问效率高,而随机访问效率较低。
#4.注意源码中的相关操作,主要是构建双向链表。
(1)LinkedList重点在于对内部类Node<E>的理解。
(2)每一个元素在LinkedList中都是在Node<E>中存储,每个Node<E>中同时还存储了当前元素的前节点和后节点。
(3)新增元素或集合,只要找到添加的位置,获取该位置的前节点pred,和后节点succ,令pred.next=新插入集合的第一个元素节点,令succ.pred=新插入的集合的最后一个元素节点即可。
(4)删除元素或集合,找到删除的位置,获取该位置的前节点pred,和后节点succ,令pred.next=succ.pred即可。
(5)注意不论是新增还是删除,均要考虑到起始节点没有pred和结束节点没有next的问题。
(6)每一个LinkedList对象中均只存了first和last两个节点,因此当根据索引寻找元素时,代码会判断索引是在前半部分还是后半部分,从而决定从first出发,还是从last出发。