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

     
  • 相关阅读:
    Install Failed Insufficient Storage, 解决 ADB 安装APK失败问题
    finding-the-smallest-circle-that-encompasses-other-circles
    PyTorch for Semantic Segmentation
    Semantic-Segmentation-DL
    Awesome Semantic Segmentation
    应用于语义分割问题的深度学习技术综述
    JS打开浏览器
    Overcoming iOS HTML5 audio limitations
    Android APP Testing Tutorial with Automation Framework
    基于方向包围盒投影转换的轮廓线拼接算法
  • 原文地址:https://www.cnblogs.com/Deters/p/11304596.html
Copyright © 2011-2022 走看看