《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增删快,查询慢,从这个方法可以知道,其实更准确的说,是在末端增加元素时才快,指定位置插入时,还是需要先进行遍历查询,速度一样慢.

 

 

posted @ 2019-09-09 15:15  草莓罐头  阅读(708)  评论(0编辑  收藏  举报