LinkedList源码分析
Java集合框架实现两个两种线性表,第一种是数组的实现ArrayList,第二种则是链表的实现LinkedList。作为链表,LinkedList具有增加和删除效率高的特点,但是其也有缺点,就是无法随机访问。
LinkedList实现了List, Deque,Cloneable以及Serializable接口。说明使用LinkedList不仅可以使用基本的链表操作,同时还能使用双端队列以及栈的操作。
LinkedList的结点实现是其内部定义的私有静态类Node。Node类代码如下:
1 private static class Node<E> { 2 E item; 3 Node<E> next; 4 Node<E> prev; 5 6 Node(Node<E> prev, E element, Node<E> next) { 7 this.item = element; 8 this.next = next; 9 this.prev = prev; 10 } 11 }
很显然从这里我们能知道,LinkedList实际上是双链表的实现。
1 transient int size = 0; 2 transient Node<E> first; 3 transient Node<E> last;
size表示链表里结点的数量,first指向链表的头结点,last指向链表的尾结点。
1 public LinkedList(Collection<? extends E> c) { 2 this(); 3 addAll(c); 4 } 5 public boolean addAll(Collection<? extends E> c) { 6 return addAll(size, c); 7 }
首先检查index是否合法,之后集合c变成Object数组,检查Object数组的大小,如果为0则添加失败。之后找到index位置的链表,从index位置开始插入,最后连接上原本的链表结点。
public boolean addAll(int index, Collection<? extends E> c) { //检查下标是否合法 checkPositionIndex(index); //将集合c转换成Object数组 Object[] a = c.toArray(); int numNew = a.length; //检查Object数组长度,如果为0说明没有需要添加的元素 if (numNew == 0) return false; //succ指向下标对应的结点,pred则是succ的前驱 Node<E> pred, succ; //如果下标正好是链表长度,则 if (index == size) { succ = null; pred = last; } else { //调用node方法返回特定下标的结点,查找方法首先根据下标是否超过链表长度一半,超过或者等于链表长度一半 //则从尾结点last开始,从尾向index查找,如果没有超过链表长度一半,则从头结点first开始,从头向index找 succ = node(index); pred = succ.prev; } //开始将Obejct数组的元素插入到index之后,首先new一个新的结点,其后继是pred,之后检查pred如果是空,说明此时newNode是头结点(?),如果pred非空 //则按照如图2.1的方式连接双链表的两个结点,一直循环直到Object数组里的元素都被连接上。 for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; } //如果succ为空,说明是在链表的结尾插入集合里的所有元素,则last指向pred if (succ == null) { last = pred; } else { //否则直接连接之前链表里的元素 pred.next = succ; succ.prev = pred; } //修改链表的长度 size += numNew; // modCount++; return true; }
add方法有两个重载的版本,一个是只有一个泛型参数的版本,也是最常用的版本,另外一个是往特定位置index插入的版本,下面分析最常用的版本。
add(E e)方法将元素用尾插法插入到链表的结尾。首先new一个前驱为last的新节点newNode,之后last指向新的尾结点newNode,最后检查之前的尾结点如果为空,则新生成的结点作为头结点,否则将原本的尾结点的后继指向newNode,并给链表的长度加一
1 public boolean add(E e) { 2 linkLast(e); 3 return true; 4 } 5 void linkLast(E e) { 6 final Node<E> l = last; 7 final Node<E> newNode = new Node<>(l, e, null); 8 last = newNode; 9 if (l == null) 10 first = newNode; 11 else 12 l.next = newNode; 13 size++; 14 modCount++; 15 }
1 public E get(int index) { 2 checkElementIndex(index); 3 return node(index).item; 4 }
首先检查下标是否合法,合法的话调用之前分析addAll时出现过的方法node(int index),这个方法返回特定下标的结点,根据下标是否超过链表大小的一半来决定,之前已经分析过了,就不再赘述。故在需要大量随机访问的情况下,还是推荐使用ArrayList实现,当然,如果链表长度不是很长,那也无所谓。
remove方法删除一个与参数相同的元素,准确的说是在链表里删除下标最小的与参数相同的元素。