推荐一篇分析LinkedList的博客,非常不错:传送门
LinkedList是基于双向链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈、队列和双端队列来使用。
LinkedList同样是非线程安全的,只在单线程下适合使用。
LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。
源码如下。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
// 链表尺寸
transient int size = 0;
/**
* 头结点
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 头结点
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/**
* Constructs an empty list.
*/
public LinkedList() {
}
/**
* 链接新元素到头部
*/
private void linkFirst(E e) {
// 记录在头部添加元素前的头指针
final Node<E> f = first;
// 创建新结点
final Node<E> newNode = new Node<>(null, e, f);
// 头结点指针指向新结点
first = newNode;
// 如果链表还是空表,则尾指针也指向新结点,否则,将新结点链接到头部
if (f == null)
last = newNode;
else
f.prev = newNode;
// 尺寸增加
size++;
modCount++;
}
/**
* 链接新元素到尾部
*/
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++;
}
/**
* 在succ结点前插入元素e
*/
void linkBefore(E e, Node<E> succ) {
// 记录succ结点的前驱
final Node<E> pred = succ.prev;
// 生成新结点,并链接到succ结点的前驱和succ结点本身。
final Node<E> newNode = new Node<>(pred, e, succ);
// 更新succ的直接前驱结点为新结点
succ.prev = newNode;
// 如果记录的succ结点的前驱为null,说明插入的位置位于头部,则让头指针指向新结点,否则更新其后驱为新结点
if (pred == null)
first = newNode;
else
pred.next = newNode;
// 尺寸增加
size++;
modCount++;
}
/**
* 断开头部f结点的链接,
*/
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;
}
/**
* 断开尾部l结点的链接
*/
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 = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
/**
* 断开结点x,并返回其item
*/
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;
}
/**
* 获取头结点元素
*/
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
/**
* 获取尾结点元素
*/
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
/**
* 移除头结点元素
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
/**
* 移除尾结点元素
*/
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
/**
* 添加元素到头结点
*/
public void addFirst(E e) {
linkFirst(e);
}
/**
* 添加元素到尾结点
*/
public void addLast(E e) {
linkLast(e);
}
/**
* 添加指定的元素到链表的尾部.
* 和addLast()方法行为一致.
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 获取指定位置的元素
*/
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
/**
* 更新指定位置的元素
*/
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
/**
* 插入新元素到指定位置
*/
public void add(int index, E element) {
// 检查插入位置是否合法
checkPositionIndex(index);
// 如果是尾部,则直接链接到尾部,否则先查找到指定为的元素,然后在其前链接元素。
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* 获取指定索引的元素
*/
Node<E> node(int 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;
}
}
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的Object[]数组
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
// 返回LinkedList的模板数组。所谓模板数组,即可以将T设为任意的数据类型
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
private static final long serialVersionUID = 876323262645176354L;
// Positional Access Operations 随机访问位置的方法
// set 设置index位置对应的节点的值为element
// get 返回LinkedList指定位置的元素
// remove 删除index位置的节点
// Search Operations 搜索方法
// indexOf
// lastIndexOf
// Queue operations. 队列方法
// peek 返回第一个节点
// element 返回第一个节点
// pool 删除并返回第一个节点
// remove 删除并返回第一个节点
// offer 添加指定元素到队列尾部
// Deque operations 双端队列方法
// offerFirst 添加元素到队列头部
// offerLast 添加元素到队列尾部
// peekFirst 返回队头元素
// peekLast 返回队尾元素
// pollFirst 返回并移除队头元素
// pollLast 返回并移除队尾元素
// push 添加元素到链表头部
// pop 移除链表头部的数据
// 从LinkedList开始向后查找,删除第一个值为元素(o)的节点
// 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点
// removeFirstOccurrence
// 从LinkedList末尾向前查找,删除第一个值为元素(o)的节点
// 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点
// removeLastOccurrence
// 返回“index到末尾的全部节点”对应的ListIterator对象(List迭代器)
// listIterator
// List迭代器
// class ListItr
}
关于LinkedList的源码,给出几点比较重要的总结:
1, 从源码中可以看出,LinkedList的实现是基于双向链表的,始终有头指针指向第一个结点,有尾指针指向最后一个结点。
2,注意两个不同的构造方法。无参构造方法中没有做任何事情,包含Collection的构造方法,将Collection中的数据加入到链表的尾部后面。
3,在查找和删除某元素时,源码中都划分为该元素为null和不为null两种情况来处理,LinkedList中允许元素为null。
4,LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容的方法。
5,源码中node(int index)方法,该方法返回双向链表中指定位置处的节点,而链表中是没有下标索引的,要指定位置出的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。源码中先将index与长度size的一半比较,如果index<size/2
,就只从位置0往后遍历到位置index处,而如果index>size/2
,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。
6,LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)。
7,在源码中提供了栈,队列,双端队列的操作方法,因此可以作为栈和双端队列使用。