LinkedList是List接口的子类,它底层数据结构是双向循环链表。LinkedList还实现了Deque接口(double-end-queue双端队列,线性collection,支持在两端插入和移除元素).所以LinkedList既可以被当作双向链表,还可以当做栈、队列或双端队列进行操作.文章目录如下:
1.LinkedList的存储实现(jdk 1.7.0_51)
下面我们进入正题,开始学习LinkedList.
LinkedList的存储实现(jdk 1.7.0_51)
我们知道LinkedList的底层数据结构是双向链表,也就是每个节点都持有指向前一个节点和指向后一个节点的指针.存储模型如下图:
理解了双向链表结构,再看源码LinkedList就会觉得很简单了。本质上,LinkedList就是这样一堆节点的集合,这些节点都持有前后节点的引用,所以LinkedList定义了一个内部类LinkedList.Node,源码如下:
private static class Node<E> { E item; Node<E> next;//前一个Node的引用 Node<E> prev;//后一个Node的引用 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
接下来,我们看看LinkedList声明的成员变量:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable { transient int size = 0;// 集合中的Node个数 transient Node<E> first;//链表中Header节点的引用 transient Node<E> last;//链表中Last节点的引用
可以看到,LinkedList定义了三个成员变量,size用于保存集合中存放节点的个数,first指向链表的第一个元素,last指向链表的最后一个元素。接下来,我们观察LinkedList的add(E)方法:
public boolean add(E e) { linkLast(e);//调用linkLast()方法处理 return true; } /** 将元素添加到链表的末尾 **/ void linkLast(E e) { final Node<E> l = last;// 获取链表的最后一个元素 // 创建新节点,新节点的prev指向l节点 final Node<E> newNode = new Node<>(l, e, null); // 因为新节点在链表的最末尾,last指向新节点 last = newNode; if (l == null)//该节点添加前,链表为空链表 first = newNode; else //如果链表中有节点 l.next = newNode; size++; modCount++; }
由上面的代码可以看到,add(E)方法是将e元素添加到链表的末尾,LinkedList还提供了addFirst(E),add(index,E).
/** 添加元素到链表的开始处 **/
public void addFirst(E e) { linkFirst(e); } private void linkFirst(E e) { final Node<E> f = first;//获取Header元素 // 创建新节点,newNode.next=f final Node<E> newNode = new Node<>(null, e, f); // first指向新节点 first = newNode; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; }
上面就是addFirst(),该方法将节点存放在链表的第一位.下面来看add(index,E)
/** 指定位置添加节点 **/ public void add(int index, E element) { checkPositionIndex(index);//index between 0 and size if (index == size)// 添加节点在链表末尾 linkLast(element); else //在index处插入新节点,newNode.next=node(index) linkBefore(element, node(index)); } /** 获取index处的Node **/ Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { index < size/2,从first开始往后找 Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { // 从last开始往前找 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } /** 在succ节点前插入新节点 **/ void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
LinkedList是基于双向链表的数据结构实现的,所以它的顺序访问会非常高效,而随机访问的效率会比较低.从node(index)方法中知道,双向链表和索引值是通过计数索引值来实现的.如果index<size/2,则从first节点开始往后查找,反之,则从last节点开始往前查找.
LinkedList的读取实现
LinkedList提供了很多方法去获取集合中的元素,我们知道实现了AbstractSequentialList接口的类都会提供索引访问.所以我们首先来看get(index).
public E get(int index) { checkElementIndex(index); return node(index).item; }
get(index)内部就是调用Node(index)处理的,这个方法上面已经讲过了,之前也说过,LinkedList是基于双向链表实现的,所以随机访问并非LinkedList所长.我们来看下LinkedList提供其他访问元素的方法:
/** 获取第一个节点 **/ 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); }