Java数据结构:LinkedList源码分析

LinkedList 简介

linkedList : 简单来说就是一个双向循环链表

如下图


分析一:LinkedList实质结构

先看源码

    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

从成员变量中可以看出 一个 头节点first,一个尾节点last,还有一个长度size,变量只有3个,可以看出,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;
        }
    }

从上面可以看出每个Node节点 中包含了一个item ,一个prev指针,一个next指针 

分析二:LinkedList构造函数

源码

    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @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);
    }

linkedList构造方法很简单,一个空构造,一个是导入式构造,addAll函数后面会讲到

OK我们下面看增删改查

分析三:LinkedList的增删改查

LinkedList : 增

增加方法中,都是基于三个基本方法实现的

代码

    /**
     * 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++;
    }

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }


一:先看第一个函数 linkFirst(E e) ,添加到头节点 

        这个分为两种情况:

        1.LinkedList 里面还没有元素,size = 0   

            不难看出  当f == null 的时候 就直接将 first 和 last 都指向 newNode

            从这里就可以看出这种情况 ,直接让头尾节点相同

       2.LinkedList 里面有元素的情况 长度为size

              这个从图例中看,更直观一点,如下图:

            

               从上面图中可以看出第一步 是将现有的头节点 first 赋值给 f  ,第二步 创建一个新的节点newNode

               并且让它的next指针指向f,第三步 将first指向newNode ,第四步 ,将f的prev指向 newNode 节点 ,

               新的链表 就形成了

二 : 添加到尾部 linkList(E e)

         跟添加到头节点一样也是有两种情况

          1. linkedList没有元素   l == null  ,  就直接将 first 和 last 都指向 newNode 

          2. linkedList有元素 ,先看图

             

           从图中来看,第一步 先将 last 赋值给 l  ,第二步,创建新节点 newNode 并且 将 newNode 的prev 指向 l 

           第三步  将last 指向 newNode  ,第四步 将l.next 指向 newNode  新的链表形成

三 : 在某个元素之前插入一个元素 linkBefore(E e, Node<E> succ)

  分两种情况  1. succ.prev = null 代表succ是第一个位置节点 就直接添加到first节点

          2.succ.prev != null 在数据中间插入 先看图

  

  图看着有点花,将就点看吧, 第一步 ,先将succ.prev 赋值给pred 

  第二步,创建一个新的节点,并让它的prev指针指向-->pred,

  它的next指针指向--> succ 

  第三步, 将succ的prev指针指向 --> newNode 

  第四步,将 pred的next指针指向 --> newNode 

  OK新的链表形成newNode 就插入到了pred 和 succ 中间

来看具体提供给外部调用的方法

方法一 : 添加到first 这个很简单 就是调用了上面说的linkFirst

    /**
     * Inserts the specified element at the beginning of this list.
     *
     * @param e the element to add
     */
    public void addFirst(E e) {
        linkFirst(e);
    }

方法二 : 直接添加一个元素到末尾 同样直接调用了 linkLast

   /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #add}.
     *
     * @param e the element to add
     */
    public void addLast(E e) {
        linkLast(e);
    }

方法三 : 直接添加一个元素 代码中可以看出 默认添加一个元素是添加到末尾,并返回ture

    /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

方法四 : 插入一个元素 

    /**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

插入一个元素 比较特别,它先是判读下标index ,方法checkPositionIndex(index),该方法判断 index >=0 && index<=size 否则抛异常 那么后面if(index == size) 只有一种情况满足 index =0&& size =0,说明LinkedList还没有添加元素,就直接执行linkLast添加到末尾,否则就执行添加到该下标之前 node(index)后面查询会将到

添加中还有一个方法 addAll(int index, Collection<? extends E> c)

    /**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element
     *              from the specified collection
     * @param c collection containing elements to be added to this list
     * @return {@code true} if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

        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;
        }

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

那些非正常情况,比如没有值,当前元素只有一个的情况,图上就不展示了

第一步,先将 要添加的集合转换成数组 a

第二步,查找出index下标的节点succ ,并找出它的prev指针 pred

第三步,将要添加的数组遍历循环

第四步,每次循环将创建一个Node节点newNode 并让它的prev指向pred ,并让pred的next指向newNode

第五步, 让newNode赋值给pred引用,让当前循环的newNode成为新的pred

第六步,循环完之后,将最新的pred的next指向succ

第七步,将succ的prev指向最新的pred也就是数组最后一次的newNode

就这样将整个集合添加到LinkedList当中

LinkedList添加所有除此之外还有一个方法,它是默认添加到末尾

    /**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the specified
     * collection's iterator.  The behavior of this operation is undefined if
     * the specified collection is modified while the operation is in
     * progress.  (Note that this will occur if the specified collection is
     * this list, and it's nonempty.)
     *
     * @param c collection containing elements to be added to this list
     * @return {@code true} if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }


LinkedList : 删除

LinkedList对外开放的删除方法有很多,归根接低都是调用下面3个方法进行删除的

方法一 :  删除 f 包括自身 之前的所有结点

    /**
     * Unlinks non-null first node f.
     */
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

步骤不多: 

               第一步,将找出first的next指针 赋值给next

               第二步,将first节点的数据置空,并将它的next指针置空

               第三步,将next节点赋值给first 让next成为新的first节点

               第四步,将next的prev指针指向=null 

               这样就能成功将first节点删除

方法二 : 删除尾节点   

    /**
     * Unlinks non-null last node l.
     */
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

步骤 一. 先将lsat的prev指针指向的节点 赋值给prev 

        二.将last的数据置空,并且将它的prev指向置空

        三.让prev成为新的last节点

        四.将prev的next指向置空


方法三 : 删除指定节点

    /**
     * Unlinks non-null node x.
     */
    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 {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

步骤 一 : 找到x的next指针指向的节点  next

        二 : 找到x的prev指针指向的节点 prev

        三 : 将prev的next指针指向节点   next

        四 : 断开x的prev指向 置空

        五 : 将next的prev指针指向 节点 prev

        六 : 将x的next指针指向置空

LinkedList : 改

修改代码比较简单

    /**
     * Replaces the element at the specified position in this list with the
     * specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

具体思路就是先查找到index下标的节点,然后将item改掉就可以了

LinkedList : 查

LinkedList查主要就看看下面这个函数方法

    /**
     * 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;
        }
    }
大体思路就是 判断index为这个链表的前半段还是后半段,然后根据头结点和尾节点,遍历到index下标的元素


总结

LinkedList 内部属于链表结构,删除 增加 只需要将对应的节点的头指针很尾指针改变就可以了

                 而查询某个元素就需要遍历这个list , 修改某个元素也需要通过遍历查询这个元素,所以查询和修改效率偏低

                 这样可以看出虽然ArrayList 和 LinkedList虽然都是实现List这个接口,但其实内部结构完全不同,从增删改查来看

                 恰恰是相反的结构特点


画图水平有待提高:o(* ̄︶ ̄*)o


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值