LinkedList是基于双向链表实现的,LinkedList同样是非线程安全的,只在单线程下适合使用。
LinkedList类声明如下:
public class LinkedList extends AbstractSequentialList implements List, Deque, Cloneable, java.io.Serializable
它继承于AbstractSequentialList,实现了List、Deque、Cloneable、 Serializable等接口。
LinkedList实现了List接口,可以对它进行队列操作;实现了Deque接口,即能将LinkedList当作双端队列使用;实现了Cloneable接口,能被克隆;实现了Serializable接口,因此它支持序列化,能够通过序列化传输。
LinkedList有两个构造方法:
// 默认构造方法
public LinkedList() { }
// 根据其他集合创建LinkedList
public LinkedList(Collection extends E> c) {
this();
addAll(c);
}
LinkedList类中的元素节点都是Node类型的:
private static class Node {
E item;
// 指向下一节点
Node next;
// 指向前一节点
Node prev;
// 构造方法
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList主要有以下几个成员属性:
// 双向队列元素个数
transient int size = 0;
// 双向队列首节点
transient Node first;
// 双向队列尾节点
transient Node last;
下面,我们主要来看一看LinkedList的add和remove方法。
public boolean add(E e) {
linkLast(e);
return true;
}
add(E)方法很简单,其中,添加元素是通过linkLast(E)方法来实现的:
void linkLast(E e) {
// 获取尾节点
final Node l = last;
// 创建新节点,新节点的前驱节点是l,后继节点是null
final Node newNode = new Node<>(l, e, null);
// 更新尾节点
last = newNode;
// 若原始链表为空,则初始化首节点
if (l == null)
first = newNode;
else
l.next = newNode;
// 更新元素个数
size++;
modCount++;
}
remove方法有两个重载方法:
// 删除元素
public boolean remove(Object o)
// 删除index下标对应元素
public E remove(int index)
remove(Object)方法比较简单,我们先来看这个方法:
public boolean remove(Object o) {
// 要删除的元素为null
if (o == null) {
for (Node x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
}
// 要删除的元素不为null
else {
for (Node x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
LinkedList删除元素时,是分为元素为null和不为null两种方式来判断的,这也说明LinkedList允许添加null元素;同时,如果这个元素在LinkedList中存在多个,则只会删除最先出现的那个。
删除元素,采用了unlink(E)方法来进行删除:
E unlink(Node x) {
// assert x != null;
// 获取要删除的元素值
final E element = x.item;
// 获取元素后继节点
final Node next = x.next;
// 获取元素前驱节点
final Node prev = x.prev;
// 若前驱节点为null,则更新首节点
if (prev == null) {
first = next;
}
// 否则,更新前驱节点的后继节点
else {
prev.next = next;
x.prev = null;
}
// 若后继节点为null,则更新尾节点
if (next == null) {
last = prev;
}
// 否则,更新后继节点的前驱节点
else {
next.prev = prev;
x.next = null;
}
// 一些清除工作
x.item = null;
// 减少元素个数值
size--;
modCount++;
return element;
}
remove(int)方法源码如下:
public E remove(int index) {
// 检查index下标的合法性
checkElementIndex(index);
// 获取元素值,并通过unlink方法删除节点
return unlink(node(index));
}
remove(int)方法会先检查index下标的合法性,然后通过node(int)方法获取对应元素节点,删除节点同样是通过unlink(E)方法,checkElementIndex(int)方法源码如下:
private void checkElementIndex(int index) {
// 若index下标不合法,则抛出IndexOutOfBoundsException异常
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 判断index下标是否合法
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
node(int)方法源码如下:
Node node(int index) {
// assert isElementIndex(index);
// 若index小于size >> 1,通过从首节点向后遍历来寻找节点
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
}
// 否则,通过从尾节点向前遍历来寻找节点
else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
该方法返回双向链表中指定下标的节点,而链表中是没有下标索引的,要找出指定位置的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。源码中先将index与size的一半比较,如果index < (size >> 1),就只从首节点向后遍历来寻找节点,否则,就从尾节点向前遍历来寻找节点。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。
public E get(int index) {
// 检查index下标的合法性
checkElementIndex(index);
// 获取元素值
return node(index).item;
}
get(int)方法会先检查index下标的合法性,然后通过node(int)方法获取对应元素节点。
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
for (Node x = first; x != null; ) {
Node next = x.next;
// 将元素值置为null
x.item = null;
// 将next置为null
x.next = null;
// 将prev置为null
x.prev = null;
x = next;
}
// 将首尾节点置为null
first = last = null;
size = 0;
modCount++;
}
clear()方法将链表元素置空,帮助GC来释放节点占用的内存。
LinkedList还实现了栈和队列的操作方法,因此也可以作为栈、队列和双端队列来使用,如push、pop、addFirst、addLast、offerFirst、offerLast等方法。这里不再介绍,实现都比较简单,有兴趣的可以看源码。
所以,ArrayList更适合用于读操作多的场景,linkedList更适合用于添加或删除数据频繁的场景。
Java集合之ArrayList详解