简介
LinkedList是基于双向链表实现的,LinkedList同样是非线程安全的,只在单线程下适合使用。
LinkedList类声明如下:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
它继承于AbstractSequentialList,实现了List、Deque、Cloneable、 Serializable等接口。
LinkedList实现了List接口,可以对它进行队列操作;实现了Deque接口,即能将LinkedList当作双端队列使用;实现了Cloneable接口,能被克隆;实现了Serializable接口,因此它支持序列化,能够通过序列化传输。
LinkedList源码详解
LinkedList有两个构造方法:
// 默认构造方法 public LinkedList() { } // 根据其他集合创建LinkedList public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
LinkedList类中的元素节点都是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; } }
我们下面主要来看一看LinkedList的add和remove方法。
add方法
public boolean add(E e) { linkLast(e); return true; }
add(E)方法很简单,其中,添加元素是通过linkLast(E)方法来实现的:
void linkLast(E e) { // 获取尾节点 final Node<E> l = last; // 创建新节点,新节点的前驱节点是l,后继节点是null final Node<E> newNode = new Node<>(l, e, null); // 更新尾节点 last = newNode; // 若原始链表为空,则初始化首节点 if (l == null) first = newNode; else l.next = newNode; // 更新元素个数 size++; modCount++; }
remove方法
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<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } // 要删除的元素不为null else { for (Node<E> 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<E> x) { // assert x != null; // 获取要删除的元素值 final E element = x.item; // 获取元素后继节点 final Node<E> next = x.next; // 获取元素前驱节点 final Node<E> 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<E> node(int index) { // assert isElementIndex(index); // 若index小于size >> 1,通过从首节点向后遍历来寻找节点 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; } }
该方法返回双向链表中指定下标的节点,而链表中是没有下标索引的,要找出指定位置的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。源码中先将index与size的一半比较,如果index < (size >> 1),就只从首节点向后遍历来寻找节点,否则,就从尾节点向前遍历来寻找节点。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。
其他相关方法
get(int)方法
public E get(int index) { // 检查index下标的合法性 checkElementIndex(index); // 获取元素值 return node(index).item; }
get(int)方法会先检查index下标的合法性,然后通过node(int)方法获取对应元素节点。
clear()方法
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<E> x = first; x != null; ) { Node<E> 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等方法。这里不再介绍,实现都比较简单,有兴趣的可以看源码。
LinkedList和ArrayList的区别
- ArrayList是基于动态数组实现的数据结构,LinkedList是基于双向链表实现的数据结构。
- 对于随机访问操作get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针找index对应的元素。
- 对于添加和删除操作add和remove,LinedList比较占优势,因为除了要找index对应的元素,ArrayList还要移动数据。
所以,ArrayList更适合用于读操作多的场景,linkedList更适合用于添加或删除数据频繁的场景。