LinkedList源码分析
众所周知,LinkedList实现了List接口,LinkedList适用于集合元素先入先出和先入后出的场景,在队列的源码中被频繁使用,我们在使用一般的队列时,就直接可以拿LinkedList来实现,如下:
LinkedList<Integer> queue = new LinkedList<>();
queue.addLast(3); //相当于入队操作
queue.addLast(4);
queue.addLast(5);
System.out.println(queue.removeFirst()); //相当于出队操作(poll)
System.out.println(queue.getFirst()); //相当于peek操作
可见LinkedList的操作十分的灵活!!
1. 整体架构
LinkedList的底层结构是一个双向链表,如下图:
看到上图的结构,注意到:
- 当链表中没有数据的时候,first和last是同一个节点,前后指向都为零;
- 双向链表大小只受内存大小的限制;
1. 类定义
先来看看这个类的定义:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
注意到了没,他还实现了Deque这个接口,所以LinkedList中还实现了poll()、peek()、push()、pop()等等,这些方法都是实现了Deque这个接口里的抽象方法,而这些方法的实现,就是基于LikedList里面本来的add、remove系列操作,比如看push()和pop()和peek()这三个方法:
public void push(E e) {
addFirst(e); //头插法新增节点
}
public E pop() {
//删除头节点
return removeFirst();
}
public E peek() {
//返回头节点
final Node<E> f = first;
return (f == null) ? null : f.item;
}
2. 底层结构
在LinedList里面有一个为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;
}
}
3. 常用方法源码解析
1. 构造方法
有两个构造方法,一个是无参构造,一个是传入一个Collection集合,注意该集合的泛型得是当前定义的泛型的子类!
public LinkedList() {
}
//该构造函数将传入的集合(符合要求的)转化为LinkedList
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
注意第二个构造函数:
- 当传入的集合泛型不符合条件时,则会编译出错;
2. 新增操作
即为双向链表,所以新增节点的操作可以为头插,也可以是尾插;
在LikedList里面,定义了两个节点:
transient Node<E> first;
transient Node<E> last;
这两个初始值都为null;
1. add()方法(尾插)
add方法默认是从尾部开始增加节点的:
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//先获取尾节点
final Node<E> l = last;
//这里通过构造方法就将newNode的prev设置为last,next设置为空
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//判断是不是空链表(插入前)
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
//记录更改
modCount++;
}