美文网首页
jdk源码LinkedList分析基于JDK10时间2018-0

jdk源码LinkedList分析基于JDK10时间2018-0

作者: 超级小学生1 | 来源:发表于2018-09-05 23:03 被阅读54次

    Linkedlist

    简介

    本节来介绍LinkedList ,他也是List 接口下最常用的用来存储数据的工具类之一。仍然从基础的数据结构,整个类的继承和实现的体系来了解。Linkedlist可以做那些事情,在哪些的应用场景下更适合应用。

    1.首先LinkedList是基于双向链表。意味着增删比较快,但是查找相对于ArrayList会比较慢。(常见面试题之一,ArrayList与LinkedList的区别),本质上来讲,就是二者的数据结构不同,稍后可以说明。

    2.实现了 Deque 接口,意味着可以进行队列操作。

    3.实现了Cloneable接口,即覆盖了函数clone(),可以被克隆。

    4.实现了 Serializable 接口,意味着LinkedList支持序列化,能通过序列化去传输。

    5.LinkedList和Arraylist一样,都是线程不安全的。

    结构图

    image.png

    构造函数

    Linkedlist共有两个构造函数。分别为:

    1.无参构造,直接创建LinkedList对象。

     /**
     * 构造一个空列表
     */
     public LinkedList() {
     }      
    
    image.png

    2.参数需要一个Collection集合,作为参数,构造一个新的集合。

     /**
     * 构造包含指定元素的列表集合,按照集合返回的顺序迭代器
     *
     * @param  c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    
    image.png
        这里就是,接收一个集合,作为参数,然后调用 addAll() 方法,来初始化LinkedList()。
    

    接下来分析,addAll()方法做了哪些事情。

    这里调用了 public 权限的 addAll() 方法。说明,我们可以将任意一个集合添加到 LinkedList 中。
    这里有一点要注意:默认的size就是当前的链表长度,默认在当前链表后面添加参数的集合。

     /**
     * 将指定集合中的所有元素追加到末尾这个列表,按照指定的返回顺序集合的迭代器。
     * 如果没有定义此操作的行为在操作中修改指定的集合进步。(注意,如果指定的集合是,就会发     * 生这种情况*这个列表,它不是空的。
     */
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
    
    Alt text

    addAll()

    这里要对Node做一下解释,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;
        }
    }  
    
    
    
    /**
    *将指定集合中的所有元素插入其中列表,从指定位置开始。变化的元素
     *目前处于该位置(如果有的话)和任何后续元素
     *右边(增加他们的指数)。新的元素将会出现
     *在列表中,以它们被返回的顺序指定集合的迭代器。
     */
    public boolean addAll(int index, Collection<? extends E> c) {
          //检查索引是否合法
        checkPositionIndex(index);
          //将添加的集合先转成Object数组
        Object[] a = c.toArray();
        int numNew = a.length;
        //如果数组的长度为0,返回false不做任何操作
        if (numNew == 0)
            return false;
            
         //定义一个节点
        Node<E> pred, succ;
        //当向链表最后面插入时。
        if (c == size) {                
            succ = null;
            //将链表最后一个节点,赋值给当前定义节点的上一个节点。
            pred = last;
        } else {
              //这里查找原来index位置的Node,做为后置节点。
            succ = node(index);
            //前置节点为index节点的前一个节点。
            pred = succ.prev;
        }
    
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //构造一个新的节点,pred原来index位置的上一个节点。e当前节点
            Node<E> newNode = new Node<>(pred, e, null);
            //如果pred为空,说明原来的链表是空的,那么新增的节点为链表的第一个节点。
            if (pred == null)
                first = newNode;
            else
            //这里是当链表不是初始化时候,替换链表内指定的node
                pred.next = newNode;
            pred = newNode;
        }
            //如果succ为空,说明在链表的末尾,(或者是链表刚刚初始化,也为null)
        if (succ == null) {
            last = pred;
        } else {
          //链表中间添加情况,更新前置节点 后置节点。
            pred.next = succ;
            succ.prev = pred;
        }
            //修改size
        size += numNew;
        //修改modCount(这里后续解释作用是什么)
        modCount++;
        return true;
    }
    

    add()添加一个元素到链表内

    /**
     * 添加一个元素到链链表中,默认追加到链表的末尾‘
     * 这里调用了public权限的add方法。’
     */
    public boolean add(E e) {
    //调用linkLast
        linkLast(e);
        return true;
    }
    

    linkLast追加到链表末尾

    /**
      *添加到链表末尾
     */
    void linkLast(E e) {
          //保存原有链表的最后一个节点。
        final Node<E> l = last;
        //构造当前节点,并且原来的最后一个节点最为当前节点的上一个节点
        final Node<E> newNode = new Node<>(l, e, null);
        //当前节点作为原有链表的最后一个节点
        last = newNode;
        //如果原有的最后一个节点为null,说明链表为null(刚刚初始化)
        if (l == null)
              //当前节点作为第一个节点。
            first = newNode;
        else
              //将当前节点赋值给原来最后一个节点的下一个节点。
            l.next = newNode;
        size++;
        modCount++;
    }
    

    remove删除元素

     /**
     *删除指定元素 
     */
    public boolean remove(Object o) {
          //如果要删除的元素为空,那么便利当前链表,找到为空的元素,解除关联。
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                        //实际从链表中去除关联
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                    //如果去除指定元素,则直接equals寻找要删除的元素。
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
    

    unlink删除元素

     /**
     * 删除指定的元素
     */
    E unlink(Node<E> x) {
        // assert x != null;
        //需要删除的节点
        final E element = x.item;
        //保存需要删除节点的下一个节点
        final Node<E> next = x.next;
        //保存需要删除节点的上一个节点
        final Node<E> prev = x.prev;
          //如果上一个节点是空,则原有的末尾节点作为第一个节点
        if (prev == null) {
            first = next;
        } else {
              //原有节点上一个节点,对应的下一个节点为next。有点绕口。。。
            prev.next = next;
            //将当前节点的 前置节点置空
            x.prev = null;
        }
        //如果后置节点为空(说明当前节点原本是尾节点)
        if (next == null) {
              //则尾节点为前置节点
            last = prev;
        } else {
            //将当前节点的 后置节点置空
            next.prev = prev;
            x.next = null;
        }
          //当前元素置为null
        x.item = null;
        //链表数量-1
        size--;
        //修改modCount
        modCount++;
        //返回删除的元素
        return element;
    }
    

    push&pop

    注意:链表中也存在push,pop操作,说明可以作为一个栈来操作。面试也很常见实现一个自己的栈或者队列。这里看一下,push和pop操作,用来了解一下栈是如何实现的。

    public void push(E e) {
        addFirst(e);
    }  
    
    public E pop() {
      return removeFirst();
    }
    

    调用的方法分别为addFirst,removeFirst。

    /**
     * 直接追加到链表最前面
     */
    public void addFirst(E e) {
        linkFirst(e);
    }
    

    pop也是一样的道理,直接操作首节点

    /**
     *删除首节点
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    

    总结

    1.上文说明了LinkedList是基于双向链表,在实际的代码中看到,当增加或者删除一个元素,在原有的链表中直接寻找并且unlink即可。

    2.删除和增加都涉及到modCount操作,这点要注意(后续解释,或者可以自行百度)。

    3.其实增加和删除或者其他操作,都围绕着一个核心,就是处理链表。这里如果把链表的结构自己理解的足够好,可以尝试自己写一个自己的链表。

    4.jdk中每一个类,方法都拆分的足够细致,得以最大化的复用。这也是是学习的目的之一,将自己的代码做到尽可能细致的拆分,每一个方法具体用来做什么。

    相关文章

      网友评论

          本文标题:jdk源码LinkedList分析基于JDK10时间2018-0

          本文链接:https://www.haomeiwen.com/subject/peazwftx.html