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

     
  • 相关阅读:
    AX ERROR: Could not find my mock parent, most likely I am stale 不及格的程序员
    利用Segue在视图控制器间传值的问题 不及格的程序员
    Creating a Singleton Instance 不及格的程序员
    iPad 通知 UIKeyboardWillShowNotification 不会在keyBoard处在Undock状态下接到通知 不及格的程序员
    Why RootViewController's view is rotated Automatically by System when the app first loaded? 不及格的程序员
    如何弹出UIDatePicker最好 不及格的程序员
    jQuery开始做恶了 不及格的程序员
    what is the SEL,id and IMP,Class ,Method? 不及格的程序员
    Objectivec 字符串比较的陷井 不及格的程序员
    Unable to create any keyboard shortcuts after the iOS 6.1.3 update on iPad. 不及格的程序员
  • 原文地址:https://www.cnblogs.com/Deters/p/11304596.html
Copyright © 2011-2022 走看看