LinkedList源码解析(JDK1.8)
LinkedList是基于链表实现的双向链表
同样我们还是先上类的关系图
从类的关系图中可以看出LinkedList继承一个抽象类和实现了四个接口,然后分别简单介绍一下:
- AbstractSequentialList:这里主要提供iterator迭代器的相关操作
- List:提供链表的增删改查、迭代器遍历等操作
- Deque:提供队首、队尾增删改查等操作
- Cloneable:按字段复制操作
- Serializable:启用其序列化功能操作
属性
属性相关的源码
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;
复制代码
transient int size = 0;//链表中存放元素的数量
transient Node first;//头节点
transient Node last;//尾节点
构造方法
/**
* Constructs an empty list.
*/
public LinkedList() {
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
复制代码
可以看出有三个构造方法:
- LinkedList():无参构造方法 _ LinkedList(Collection<? extends E> c):集合c直接设置链表顺序的构造方法
节点
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;
}
}
复制代码
其中element表示存储的Value值,pre、next分别指向当前节点的上一个节点和下一个节点,由此也可以看出来是双向的
增删改查
我们实际项目中,围绕LinkedList做的最多的就是增删改查相关操作,我们将细细的品一品官方的实现方式
拆入元素
单个元素拆入
拆入元素先介绍最简单的单个拆入元素
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//得到当前的最后一个元素节点
final Node<E> l = last;
//构造新节点,并设置当前上一个节点是l,元素为e,下一个节点为null
final Node<E> newNode = new Node<>(l, e, null);
//现在最新的last节点为新插入的节点
last = newNode;
//判断l节点是否为空,为空的话则代表当前插入的是第一个元素,则需要设置首节点也为当前的新节点,反之则设置原先的最后一个节点的next节点是当前新的节点
if (l == null)
first = newNode;
else
l.next = newNode;
//元素数量+1
size++;
//链表改动次数+1
modCount++;
}
复制代码
从上面可以看出,直接add(e)是非常简单的,我们接下来来看一下其他的拆入元素的方法
下标位置拆入节点
public void add(int index, E element) {
//下标拆入都先判断下标是否越界,越界就抛出异常
checkPositionIndex(index);
//如果拆入位置是尾部index,和上面的直接拆入元素调用的同一个方法就不分析来,来看看linkBefore的拆入
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
//succ拿的来当前index下标的节点
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//得到index下标节点的前一个
final Node<E> pred = succ.prev;
//构造新节点,新节点的前一个节点为原先这个index位置的前一个节点,设置新节点的下一个节点为原先index节点
final Node<E> newNode = new Node<>(pred, e, succ);
//设置原先index位置的节点的前一个为新节点
succ.prev = newNode;
//如果pred节点为null,则代表succ是头节点,现在设置头节点为新节点
if (pred == null)
first = newNode;
else
//如果pred节点不为null,则修改pred的下一个节点为新节点
pred.next = newNode;
//元素数量+1
size++;
//链表修改次数+1
modCount++;
}
复制代码
addFirst()、addLast()也是新简单的,源码进去看一下就行,我们来讲解一下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;
//拆入位置是size,即接着尾部拆入
if (index == size) {
//当前节点没有,前一个节点就是当前最后一个节点
succ = null;
pred = last;
} else {
//当前节点就是index位置的节点,前一个节点直接拿prev获取
succ = node(index);
pred = succ.prev;
}
//快速for处理待拆入元素的数组
for (Object o : a) {
//元素转换
@SuppressWarnings("unchecked") E e = (E) o;
//构建新节点的前一个是pred,元素为e,下一个节点为null
Node<E> newNode = new Node<>(pred, e, null);
//如果pred节点为null,则代表原先index位置的节点为头节点,则需要重新设置头节点
if (pred == null)
first = newNode;
else
//如果pred节点不为null,则设置原先index位置的节点的下一个节点为当前新节点
pred.next = newNode;
//一个新节点完成后,设置上一个为当前新节点,直到把所有元素拆入完成
pred = newNode;
}
//index位置的原先节点为null,则代表index原先的前一个节点就是最后一个节点,则设置最后一个节点就是最新最后的那个节点
if (succ == null) {
last = pred;
} else {
//index位置的原先节点不为null,则设置拆入数组的最后一个元素的最后一个节点的next是succ,succ的前一个是当前这个
pred.next = succ;
succ.prev = pred;
}
//得到新链表元素的数量
size += numNew;
//链表改动次数+1
modCount++;
return true;
}
复制代码
那直接addAll(Collection<? extends E> c)就更不用说来,从中可以看出LinkedList是没有长度限制的,LinkedList的拆入和ArrayList的拆入相比更快速
删除元素
单个元素删除
public boolean remove(Object o) {
//删除的元素为null
if (o == null) {
//从头节点开始查找,如果item相等则进行unlink操作删除这个节点
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;
}
//x为需要删除的节点
E unlink(Node<E> x) {
// assert x != null;
//得到x节点元素
final E element = x.item;
//拿到前一个节点,后一个节点
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//前一个节点为null,则代表x为头节点
if (prev == null) {
//设置新的头节点
first = next;
} else {
//设置prex.next为x.next
prev.next = next;
//x的前一个节点为null
x.prev = null;
}
//如果后一个节点为null,则代表x为尾节点
if (next == null) {
//设置新的尾节点
last = prev;
} else {
//设置x.next.prev为x.prev
next.prev = prev;
//x的后一个节点为null
x.next = null;
}
//设置x节点的元素为null,帮助gc回收
x.item = null;
//链表元素-1
size--;
//改动次数+1
modCount++;
return element;
}
复制代码
直接删除某个元素讲完来,我们来讲一下根据index下标删除元素
public E remove(int index) {
//下标都先判断下标是否越界,越界就抛出异常
checkElementIndex(index);
return unlink(node(index));
}
Node<E> node(int index) {
// assert isElementIndex(index);
//二分查找的思想,判断一下是在链表的前半部分还是后半部分
if (index < (size >> 1)) {
//从头节点一直找到index位置拿到节点
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//从尾节点一直找到index位置拿到节点
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
复制代码
remove(int index)也是很好理解的,remove()和removeFirst()都是从头节点开始删除,removeLast()删除尾节点
更新元素
public E set(int index, E element) {
//只要有index,都先检验是否越界
checkElementIndex(index);
//node上面分析过就是为了找到index位置的节点
Node<E> x = node(index);
//设置新节点的元素,并返回原先的老元素
E oldVal = x.item;
x.item = element;
return oldVal;
}
复制代码
查找元素
get()里面的方法前面都介绍过了
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
复制代码
整体看来LinkedList的实现也是很简单的,我们可以看出来LinkedList增删毕竟快,改查比较慢,我们和ArrayList对比分析便于理解,两者的增删改查的快慢完全相反的。因此也可以得出在实际项目中,我们需要频繁增删数据的用LinkedList,我们需要频繁改查数据的用ArrayList
其他方法
像contains()、clear()这几个我们也来简单的看一下
public boolean contains(Object o) {
return indexOf(o) != -1;
}
public int indexOf(Object o) {
int index = 0;
if (o == 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;
}
和上面分析过的node()方法及其相似
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
//头节点开始循环,设置元素,前一个节点、后一个节点都为null
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
复制代码
线程安全问题
- LinkedList是线程不安全的
- Collections.synchronizedList()可实现LinkedList的线程安全
- ConcurrentLinkedDeque是线程安全的
我们要对比上面的这些相关的来便于我们理解和分析,然后还需要结合ArrayList进行对比分析