zoukankan      html  css  js  c++  java
  • 容器源码分析之——LinkedList

    一、LinkedList简介

    LinkedList和ArrayList与Vector一样,实现了Lits接口,但它执行某些操作如插入(此处指随机插入,如果是依次在末尾插入,不一定效率更高)、和删除元素操作比ArrayList与Vector更高效,而随机访问操作效率低。除此之外,LinkedList还添加了可以使其用作栈、队列或双端队列的方法。LinkedList在实现方面与ArrayList与Vector有哪些不同呢?为什么它插入删除操作效率高?随机访问操作效率低?它是如何用作栈、队列或双端队列的?本文将分析LinkedList的内部结构及实现原理,帮助大家更好的使用它,并一一解答上述问题。

    1.1 类继承结构

    Alt Text

    1.2 数据结构

    LinkedList底层的数据结构是基于双向循环链表的,JDK1.7之前头结点不存储数据,而1.7之后头结点是存储数据的。

    Alt Text
    同理,查看尾节点的注释也是这样的意思。
    链表中的节点组成如下:
    Alt Text

    二、源码

    2.1 注释

    LinkedList是List接口和Deque接口的双向链表实现。它实现了所有的列表操作,允许所有的元素(包括null)。
    所有对LinkedList的操作都看作是对双向链表的操作。操作需要遍历时需要从头或者尾开始,具体选头还是尾,取决于指定的索引离哪个更近。
    注意此实现是线程不同步的。
    “Collections.synchronizedList”可以在LinkedList创建之初将其包装起来,以保证线程安全。
    List list = Collections.synchronizedList(new LinkedList(...))
    LinkedList返回的iterator挥着listIterator都是fail-fast的。

    2.2 定义

    public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    
    

    类定义与类继承结构图是对应的。
    从中我们可以了解到:

    • LinkedList :说明LinkedList支持泛型。
    • extends AbstractSequentialList :AbstractSequentialList继承了AbstractList。AbstractList提供List接口的骨干实现,以最大限度地减少“随机访问”数据存储(如ArrayList)实现Llist所需的工作。但AbstractSequentialList 只支持按次序访问,而不像 AbstractList 那样支持随机访问。这是LinkedList随机访问效率低的原因之一。
    • implements List:实现了List。实现了所有可选列表操作。
    • implements Deque:Deque,Double ended queue,双端队列。LinkedList可用作队列或双端队列就是因为实现了它
    • implements Cloneable:表明其可以调用clone()方法来返回实例的field-for-field拷贝。
    • implements java.io.Serializable:表明该类具有序列化功能。
      与ArrayList对比发现,LinkedList并没有实现RandomAccess,而实现RandomAccess表明其支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。这是LinkedList随机访问效率低的原因之一。

    2.3 域

     /**
     * LinkedList节点个数
     */
    transient int size = 0;
    
    /**
     * 指向头节点的指针
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;
    
    /**
     * 指向尾节点的指针
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;
    
    

    2.4 构造方法

    /**
     * 构造函数1:构造一个空链表.
     */
    public LinkedList() {
    }
    
    /**
     * 构造函数2:根据指定集合c构造linkedList。先构造一个空linkedlist,
     * 在把指定集合c中的所有元素都添加到linkedList中。
     *
     * @param  c 指定集合
     * @throws NullPointerException 如果特定指定集合c为null
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    
    

    共有两个构造函数

    1. LinkedList()
    2. LinkedList(Collection<? extends E> c)

    2.5 核心方法

    2.5.1 getFirst()

    /**
     * 返回链表中的头结点的值.
     *
     * @return 返回链表中的头结点的值
     * @throws NoSuchElementException 如果链表为空
     */
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
    
    

    2.5.2 getLast()

    /**
     * 返回链表中的尾结点的值.
     *
     * @return 返回链表中的头结点的值
     * @throws NoSuchElementException 如果链表为空
     */
    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
    
    

    2.5.3 removeFirst()

       /**
     * 删除并返回表头元素.
     *
     * @return 表头元素
     * @throws NoSuchElementException 链表为空
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    
      private E unlinkFirst( Node<E> f) {
        // assert f == first && f != null;
        // 保存头结点的值
        final E element = f.item;
        // 保存头结点指向的下个节点
        final Node<E> next = f.next;
        //头结点的值置为null
        f.item = null;
        //头结点的后置指针指向null
        f.next = null; // help GC
        //将头结点置为next
        first = next;
        //如果next为null,将尾节点置为null,否则将next的后置指针指向null
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        //返回被删除的头结点的值
        return element;
    }
    
    

    核心方法其实在于unlinkFirst(Node f)。具体的步骤实际上非常简单,在注释中也很清楚。对着这个数据结构尝试自己画一画,注意只有一个元素的情况,删除之后fist == null && last == null

    Alt Text

    2.5.4 removeLast()

    /**
     * 删除并返回表尾元素.
     *
     * @return 表尾元素
     * @throws NoSuchElementException 链表为空
     */
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    
    /**
     * 删除尾节点l.并返回尾节点的值
     */
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        // 保存尾节点的值
        final E element = l.item;
        //获取新的尾节点prev
        final Node<E> prev = l.prev;
        //旧尾节点的值置为null
        l.item = null;
        //旧尾节点的后置指针指向null
        l.prev = null; // help GC
        //将新的尾节点置为prev
        last = prev;
        //如果新的尾节点为null,头结点置为null,否则将新的尾节点的后置指针指向null
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        //返回被删除的尾节点的值
        return element;
    }
    
    

    2.5.5 addFirst(E e)

      /**
     * 在表头插入指定元素.
     *
     * @param e 插入的指定元素
     */
    public void addFirst(E e) {
        linkFirst(e);
    }
    
    /**
     * 在表头添加元素
     */
    private void linkFirst(E e) {
        //使节点f指向原来的头结点
        final Node<E> f = first;
        //新建节点newNode,节点的前指针指向null,后指针原来的头节点
        final Node<E> newNode = new Node<>(null, e, f);
        //头指针指向新的头节点newNode 
        first = newNode;
        //如果原来的头结点为null,更新尾指针,否则使原来的头结点f的前置指针指向新的头结点newNode
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }
    
    

    2.5.6 addLast(E e)

      /**
     * 在表尾插入指定元素.
     * 
     * 该方法等价于add()
     *
     * @param e 插入的指定元素
     */ 
    public void addLast(E e) {
        linkLast(e);
    }
    /**
     * 在表尾插入指定元素e
     */
    void linkLast(E e) {
        //使节点l指向原来的尾结点
        final Node<E> l = last;
        //新建节点newNode,节点的前指针指向l,后指针为null
        final Node<E> newNode = new Node<>(l, e, null);
        //尾指针指向新的头节点newNode
        last = newNode;
        //如果原来的尾结点为null,更新头指针,否则使原来的尾结点l的后置指针指向新的头结点newNode
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
    
    
    

    2.5.7 contains(Object o)

     /**
     * 判断链表是否包含指定对象o
     * @param o 指定对象
     * @return 是否包含指定对象
     */
    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }
    /**
     * 正向遍历链表,返回指定元素第一次出现时的索引。如果元素没有出现,返回-1.
     * 
     * @param o 需要查找的元素
     * @return 指定元素第一次出现时的索引。如果元素没有出现,返回-1。
     */
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }
    
    

    LinkedList中还有很多其它的方法,我不再一一举出源码,因为总体的方法逻辑是比较简单的,下面希望根据LinkedList除了可以用作列表还有可用做队列,栈,双端队列这个特点,对其方法进行分类。

    Alt Text

  • 相关阅读:
    希望走过的路成为未来的基石
    第三次个人作业--用例图设计
    第二次结对作业
    第一次结对作业
    第二次个人编程作业
    第一次个人编程作业(更新至2020.02.07)
    Springboot vue 前后分离 跨域 Activiti6 工作流 集成代码生成器 shiro权限
    springcloud 项目源码 微服务 分布式 Activiti6 工作流 vue.js html 跨域 前后分离
    spring cloud springboot 框架源码 activiti工作流 前后分离 集成代码生成器
    java代码生成器 快速开发平台 二次开发 外包项目利器 springmvc SSM后台框架源码
  • 原文地址:https://www.cnblogs.com/cleverziv/p/14349671.html
Copyright © 2011-2022 走看看