zoukankan      html  css  js  c++  java
  • LinkedList源码分析

            Java集合框架实现两个两种线性表,第一种是数组的实现ArrayList,第二种则是链表的实现LinkedList。作为链表,LinkedList具有增加和删除效率高的特点,但是其也有缺点,就是无法随机访问。

            下面就结合LinkedList常用的方法对该实现的源码进行分析。

    1. 概括

            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实际上是双链表的实现。

            在LlinkedList类里关键的属性有以下三个:

    1     transient int size = 0;
    2     transient Node<E> first;
    3     transient Node<E> last;

            size表示链表里结点的数量,first指向链表的头结点,last指向链表的尾结点。

    2. 构造器

            LinkedList提供了两个构造器,一个是无参的默认构造器,没有执行任何操作,另外一个是参数为Collection子类的构造器,该Collection子类的实际类型必须是LinkedList的泛型的子类,该构造器代码如下:

    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     }

            这个构造器调用重载的addAll方法,将集合c的所有元素添加到链表的结尾。实质上是调用另外一个版本的addAll方法。下面具体展开。

            首先检查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;
        }

     

    3. add方法

            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     }

    4. get与contains方法

            许多人在使用LinkedList的时候以为其实现的get方法能随机访问,实际上LinkedList实现的get方法还是要遍历链表,其代码如下:

    1 public E get(int index) {
    2         checkElementIndex(index);
    3         return node(index).item;
    4     }

            首先检查下标是否合法,合法的话调用之前分析addAll时出现过的方法node(int index),这个方法返回特定下标的结点,根据下标是否超过链表大小的一半来决定,之前已经分析过了,就不再赘述。故在需要大量随机访问的情况下,还是推荐使用ArrayList实现,当然,如果链表长度不是很长,那也无所谓。

            contains方法当链表里存在参数指定的对象时返回true,这里的存在,指的是在参数对象o非空的情况下,存在至少一个链表结点里的item能使得o.equals(item)为真。contains方法本质上是调用indexOf方法,即返回特定元素被保存在链表结点的下标,indexOf方法如果找不到则返回-1。

    5. remove方法

            remove方法删除一个与参数相同的元素,准确的说是在链表里删除下标最小的与参数相同的元素。

            删除的方法是从头向尾遍历链表,找到首次出现的,与参数相同的元素,调用unlink方法,在双链表中断开一个结点(具体过程比较简单就不展开了),之后将被删除的元素存在的结点的item置为null,并返回被删除的元素。

  • 相关阅读:
    redis 系列27 Cluster高可用 (2)
    redis 系列26 Cluster高可用 (1)
    redis 系列25 哨兵Sentinel (高可用演示 下)
    redis 系列24 哨兵Sentinel (中)
    redis 系列23 哨兵Sentinel (上)
    (网页)jQuery判断checkbox是否选中的方法
    (后端)swagger
    (其他)2018下半年目标
    (后端)Sql Server日期查询-SQL查询今天、昨天、7天内、30天(转)
    (网页)HTML中INPUT type="date"标签如何赋值注意问题(转)
  • 原文地址:https://www.cnblogs.com/mlcn-2000/p/14903649.html
Copyright © 2011-2022 走看看