Collection常用子类源码阅读-List子类篇:LinkedList

左心房为你撑大大i 提交于 2019-12-26 17:11:41

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

类图关系:

LinkedList底层存储数据结构:

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

LinkedList的属性变量:

//长度
transient int size = 0;
//第一个元素
transient Node<E> first;
//最后一个元素
transient Node<E> last;

一、构造方法:

LinkedList的构造方法,除了一个无参构造方法,还有一个以Collection 实例为入参的构造方法,内部调用了allAll(Collection实例)方法,后面再细说吧。看过LinkedList的底层数据结构和属性变量之后,在这里需要细说一下LinkedList的结构:

一个Node节点对象,它的next属性引用指向下一个节点,prev属性引用指向上一个节点,因此节点与节点之间形成一个链表的结构,不管从哪个节点开始,通过这两个属性都可以找到链表中其他所有的节点,所以针对于LinkedList底层元素的操作,其实就是针对节点的next和prev的引用指向的修改

同样的,我们可以想到first作为链表的第一个节点,肯定是没有前一个节点的,所以其属性prev引用指向null,同样的道理,last的next属性引用也指向null。

画个图会比较好理解一些:

二、常用方法解析:

粗略浏览一遍Linked的源码,发现大多数常用方法都是调用几个非public方法,那先看看里面的非public方法:

1.linkFirst(E e):向链表的首部添加一个节点,入参e作为该节点的值

    //把e作为第一个节点的值关联起来
    private void linkFirst(E e) {
        //获取第一个节点的副本f
        final Node<E> f = first;
        //新建一个节点,将副本f作为下一个节点,e作为本节点的值,前一个节点为null
        final Node<E> newNode = new Node<>(null, e, f);
        //将新建的节点赋值给first,使其作为第一个节点
        first = newNode;
        //如果f为null,说明是第一次增加节点,整个链表只有一个节点,最后一个节点也就是第一个节点,赋值给last
        if (f == null)
            last = newNode;
        else
        //如果f不为null,此时f作为第一个节点的下一个节点,需要将其的属性prev引用指向第一个节点,即newNode
            f.prev = newNode;
        //长度+1
        size++;
        //修改次数+1
        modCount++;
    }

2.linkLast(E e):向链表尾部新增一个节点,入参e作为该节点的值

    void linkLast(E e) {
        //拷贝尾部节点的副本l
        final Node<E> l = last;
        //新建一个节点,使其上一个节点为副本l,下一个节点为null
        final Node<E> newNode = new Node<>(l, e, null);
        //将该节点重新赋值给尾部节点
        last = newNode;
        //如果副本l为null,说明是第一次添加元素,first也指向新建的节点元素
        if (l == null)
            first = newNode;
        else
        //如果副本l不为null,将其下一个节点的引用指向新的尾部节点
            l.next = newNode;
        size++;
        modCount++;
    }

3.linkBefore(E e,Node<E>succ): 将元素e插入到一个不为空的节点的前面

    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        //获取succ节点的前一个节点作为副本pred
        final Node<E> pred = succ.prev;
        //新建一个节点,使其prev指向副本pred,也就是原节点succ的prev
        //            使其next指向原节点succ本身,e作为该节点的值
        final Node<E> newNode = new Node<>(pred, e, succ);
        //修改原节点succ的prev的引用指向新建节点
        succ.prev = newNode;
        
        //如果副本pred为null,说明原节点succ在做修改之前就是first的引用,
        //修改之后,原节点的前一个节点应该为第一个节点
        if (pred == null)
            first = newNode;
        else
        //如果pred不为null,将pred的next引用指向新建的节点
            pred.next = newNode;
        size++;
        modCount++;
    }

4.unlinkFirst(Node<E> f): 删掉第一个节点(f为第一个节点作为参数传进方法)

    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        //获取节点f的值element
        final E element = f.item;
        //获取节点f的下一个节点作为副本next(即第二个节点)
        final Node<E> next = f.next;
        //以下两步赋值走完,f此时属性全为null(确保参数f是第一个节点first的情况下)
        f.item = null;
        f.next = null; // help GC
        //将next赋值给first变量,此时第二个节点就变成了第一个节点
        first = next;
        //如果next为null,说明对象实例的节点只有一个,以上操作将去掉唯一一个节点的引用.first和next都为null
        if (next == null)
            last = null;
        else
        //如果next不为null,此时next作为第一个节点,prev引用原指向从f变成指向null,
        //至此,第一个节点f将没有变量进行引用,将会被GC回收
            next.prev = null;
        size--;
        modCount++;
        //返回保存下来的element的值
        return element;
    }

5.unlinkLast(Node<E>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;
        l.prev = null; // help GC
        //改变last的引用指向为prev
        last = prev;
        if (prev == null)
        //如果prev为null,说明以上操作是删除本对象实例的唯一一个节点,实例中不再包含节点,first和last都为null
            first = null;
        else
        //将最后一个节点的next赋值为null,至此,原最后一个节点l将没有任何变量引用,会被GC回收
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

6.unlink(Node<E> x): 将节点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;
        
        //prev为null说明节点x是第一个元素,去掉该元素意味着第二个节点将作为first的引用
        if (prev == null) {
            first = next;
        } else {
        //设置节点x的上一个元素prev的下一个元素next 指向x的下一个元素next
        //看起来读起来有点绕,其实就是将节点x去掉,那节点x的上下节点要连接起来,就要修改上下节点的next和prev属性
        //这一步是修改上节点的next属性
            prev.next = next;
        //设置x的prev为null 
            x.prev = null;
        }
        
        //next为null,说明节点x是最后一个元素,去掉该元素意味着x的上节点将作为last的引用
        if (next == null) {
            last = prev;
        } else {
        //同上,这一步是修改x节点下节点的prev属性,
        //这一步修改完成后,将没有任何变量的引用指向x,方法执行完,x将会被GC回收
            next.prev = prev;
            x.next = null;
        }
        x.item = null;
        size--;
        modCount++;
        return element;
    }

好了,分析完这几个非public方法,基本上就已经了解了LinkedList对底层数据的操作是怎么一回事了,再详细看看常用的方法吧:

7.获取方法:基本上都是调用以上几个非public方法

//获取第一个元素
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

//获取最后一个元素
public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}
//获取下标第index个元素
public E get(int index) {
    //检查index是否在0-size的范围内
    checkElementIndex(index);
    return node(index).item;
}
//获取第index个元素
    Node<E> node(int index) {
        // assert isElementIndex(index);
        //LinkedList的变量中只有第一个和最后一个节点,获取元素只有从这两个变量着手
        //先做一次判断,用index和size/2的关系,来判断顺序还是倒序查找元素
        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;
        }
    }
//获取值在链表中的第一次出现的位置(顺序查找)
    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;
    }
//获取某个元素在链表中最后一次出现的位置(倒序查找)
    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }
//嗯...获取第一个元素,并且会移除第一个元素,FIFO的概念
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

8.新增

    //直接调用linkLast
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    //在链表尾部添加集合元素
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
    //从某个位置开始添加元素集合
    public boolean addAll(int index, Collection<? extends E> c) {
        //判断index在不在0-size之间
        checkPositionIndex(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;
        //将链表在index位置拆开,分成两个链表,
        //阅读完代码可知:pred表示前半段链表的最后一个节点,succ表示后半段链表的第一个节点
        Node<E> pred, succ;
        
        if (index == size) {
            //在链表尾部添加元素
            //那么pred(前半段)为最后一个节点,succ(后半段)为null
            succ = null;
            pred = last;
        } else {
            //succ(后半段)为下标index位置的元素,node(index)方法在获取方法中已经解析
            succ = node(index);
            //pred(前半段)为succ的前一节点
            pred = succ.prev;
        }
        
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //每循环一个数组的元素,将新建一个节点,修改节点的前一节点为pred(前半段),
            //新建节点为(前半段+新元素)
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                //如果pred为null,说明是在第一个位置添加元素,first的引用应指向新建的节点
                first = newNode;
            else
                //pred不为null,那将修改pred(前半段链表)的next为当前节点
                pred.next = newNode;
            //重置前半段链表为新建节点(原前半段+新元素)
            pred = newNode;
        }
        //循环走完后,pred为原前半段+数组中所有元素组成的新链表的最后一个节点,
        //此时该节点的next仍然没有和后半段链表相连
        
        //如果succ(后半段)为null,那么pred就是最后的新链表的最后一个节点了,赋值给last即可
        if (succ == null) {
            last = pred;
        } else {
        //succ不为null,将彼此的next和prev互相引用即可
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }
    //设置指定下标节点的值,并返回原来的值
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
    //在指定下标出新增节点
    public void add(int index, E element) {
        //老规矩,先做判断
        checkPositionIndex(index);
        //如果index==size,直接在尾部添加即可
        if (index == size)
            linkLast(element);
        else
        //直接指定下标节点前添加元素,那添加的元素的下标就为index了,原下标index的元素会往后移一位
            linkBefore(element, node(index));
    }

9.删除

    //删除元素 是删除第一个元素 并返回该元素的值
    public E remove() {
        return removeFirst();
    }
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    //删除指定元素,只会删除第一个匹配的元素
    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) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
    //删除指定下标的元素
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

还有一些方法如pop(),push()之类的,都是调用上面那些非public方法,就不再赘述了

三、迭代器

LinkedList的迭代器是实现在类里的内部类:

    private class ListItr implements ListIterator<E> {
        //最后一个被返回的节点
        private Node<E> lastReturned;
        //下一个将要被返回的节点
        private Node<E> next;
        //下一个将要被返回的节点的下标
        private int nextIndex;
        private int expectedModCount = modCount;
        //如果指定下标index,则该迭代器从下标index开始迭代,next指向下标index的节点即可
        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }

        public boolean hasNext() {
            return nextIndex < size;
        }
        
        public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();
            //链表结构迭代非常方便,调用一次,先保存next的副本,直接给next指向下一节点next.next,
            //返回副本的值即可
            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }

        //移除当前元素
        public void remove() {
            checkForComodification();
            if (lastReturned == null)
                throw new IllegalStateException();
            //直接调用unlink(E e),在链表中删除该元素,再修改变量属性next,lastReturned,nextIndex的值即可
            Node<E> lastNext = lastReturned.next;
            unlink(lastReturned);
            //一般情况下next为lastReturned的下一节点,如果出现两者引用地址一样的话,
            //应该是多线程的影响,重新指向lastReturned的下一个节点
            if (next == lastReturned)
                next = lastNext;
            else
                nextIndex--;
            lastReturned = null;
            expectedModCount++;
        }
        //忽略其他方法
    }

对比ArrayList的迭代器,因为LinkedList的数据结构的特殊性,LinkedList迭代器就方便很多。

 

END

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!