《JDK源码阅读三》--LinkedList双向链表详解
先简单描述一下LinkedList的数据结构和特性:
LinkedList和ArrayList都实现了List接口,但LinkedList底层是双向链表,所以不存在索引,
查询时:LinkedList需要从链表头部或者链表尾部遍历查询所有节点,所以查询较慢,
删除时:LinkedList只需要改变指针的指向,并把要删除的节点置为null,即可,不需要改变元素位置,所以删除较快.(图中所有的地址,表示节点的地址)
1.先看看LinkedList的属性都有哪些:
transient int size = 0; // 储存的节点个数 transient Node<E> first; // 指定第一个节点的指针 transient Node<E> last; // 指定最后一个节点的指针
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; } }
2.LinkedList的构造方法:
/** * 创建一个空list */ public LinkedList() { } /** * 按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。*/ public LinkedList(Collection<? extends E> c) { this(); // 先调用无参构造方法,创建一个空列表 addAll(c); // 然后调用addAll()方法,将集合中的元素按照顺序,逐一插入链表中 }
3.一些关键方法
Node<E> node(int index) 获取索引处的Node节点
/** * Returns the (non-null) Node at the specified element index. */ Node<E> node(int index) { // assert isElementIndex(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; } }
这个方法解释了为什么LinkedList查询慢,因为链表没有所有索引,只有头节点和尾结点,查询元素时,判断索引在整个链表是偏左还是偏右,进行遍历查找,然后将其返回
private void linkFirst(E e) 将该元素作为链表的第一个元素
/** * Links e as first element. */ 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) 将该元素作为链表的最后一个元素
void linkLast(E e) { final Node<E> l = last; // 因为l看不清,说明的时候用大写L替换 final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
这两个方法很相似,以linkLast(E e)方法为例讲解,新建节点L指向last,表示为链表原末端节点,新建节点newNode,放到链表末端,其prev指针指向L,next指针设为null,表示为新的末端节点
判断原末端节点L是否为null,若为空说明,未初始化链表头节点,此时将newNode作为头结点,若不为空,将原末端节点的next指针,指向新节点newNode.
E unlink(Node<E> x) 取消该非空节点链接
1 /** 2 * Unlinks non-null node x. 3 */ 4 E unlink(Node<E> x) { 5 // assert x != null; 6 final E element = x.item; 7 final Node<E> next = x.next; 8 final Node<E> prev = x.prev; 9 10 if (prev == null) { 11 first = next; 12 } else { 13 prev.next = next; 14 x.prev = null; 15 } 16 17 if (next == null) { 18 last = prev; 19 } else { 20 next.prev = prev; 21 x.next = null; 22 } 23 24 x.item = null; 25 size--; 26 modCount++; 27 return element; 28 }
首先获取当前节点的上一个节点prev和下一个节点next,
如果上一个节点prev为空,说明当前节点为头节点,需要将当前节点的下一个节点next设置为头节点.
否则,将当前节点的上一个节点prev的next指针,指向当前节点的下一个节点;
如果下一个节点next为空,说明当前节点为末端节点,需要将当前节点的上一个节点prev设置为末端节点.
否则,将当前节点的下一个节点next的prev指针,指向当前节点的上一个节点;
然后,节点元素设置为null,等待垃圾回收器回收.
public boolean addAll(int index, Collection<? extends E> c) 将集合中所有的元素,插入到链表的指定位置,并按照迭代器返回的顺序显示
1 /** 2 * Inserts all of the elements in the specified collection into this 3 * list, starting at the specified position. Shifts the element 4 * currently at that position (if any) and any subsequent elements to 5 * the right (increases their indices). The new elements will appear 6 * in the list in the order that they are returned by the 7 * specified collection's iterator. 8 * 9 * @param index index at which to insert the first element 10 * from the specified collection 11 * @param c collection containing elements to be added to this list 12 * @return {@code true} if this list changed as a result of the call 13 * @throws IndexOutOfBoundsException {@inheritDoc} 14 * @throws NullPointerException if the specified collection is null 15 */ 16 public boolean addAll(int index, Collection<? extends E> c) { 17 checkPositionIndex(index); // 索引越界验证 18 19 Object[] a = c.toArray(); 20 int numNew = a.length; 21 if (numNew == 0) 22 return false; 23 24 Node<E> pred, succ; 25 if (index == size) { // 在末端节点后插入元素 26 succ = null; 27 pred = last; 28 } else { 29 succ = node(index); 30 pred = succ.prev; // 获取指定位置节点的上一节点 31 } 32 33 for (Object o : a) { // 遍历集合中所有的元素, 34 @SuppressWarnings("unchecked") E e = (E) o; 35 Node<E> newNode = new Node<>(pred, e, null); 36 if (pred == null) // 说明链表为空链表,设置头部节点 37 first = newNode; 38 else 39 pred.next = newNode; // 当前索引处节点的上一节点的next指针,指向新节点 40 pred = newNode; 41 } 42 43 if (succ == null) { // 如果为在末端节点后插入,那么设置末端节点 44 last = pred; 45 } else { 46 pred.next = succ; // 将新插入完成的最后一个节点和索引处的原节点进行关联 47 succ.prev = pred; 48 } 49 50 size += numNew; 51 modCount++; 52 return true; 53 }
原来一直说LinkedList增删快,查询慢,从这个方法可以知道,其实更准确的说,是在末端增加元素时才快,指定位置插入时,还是需要先进行遍历查询,速度一样慢.