【JDK源码】LinkedList源码分析

目录

一、依赖关系

二、LinkedList结构分析

三、LinkedList增删与查询操作的性能分析

四、增删改查API对比整理

五、迭代器

六、分割器

小结


一、依赖关系

LinkedList是一种基于链表实现的List集合,继承和实现关系如下:

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

1、AbstractSequentialList类、AbstractList类

AbstractSequentialList类是LinkedList类的抽象父类,同时它又继承了AbstractList类,AbstractSequentialList类中通过列表迭代器ListIterator 实现了get()、set()、add()、remove()等功能,为LinkedList集合按次序访问元素提供了支持,而AbstractList类则提供了随机访问的支持,像ArrayList、Vector等具有随机访问特性的集合则直接继承了AbstractList类。

2、List接口、Deque接口

实现了List接口,说明LinkedList类具备了List集合通用的特性,比如可以对集合元素进行添加add、删除remove、获取get、修改set、排序sort、列表迭代listIterator等基本操作。实现的Deque接口,是一个双端队列接口,说明LinkedList类具备了Deque队列通用的特性,比如可以从 队列头部 或 队列尾部 对集合元素进行添加(如add/offer/push),删除(如remove/poll/pop),获取(如get/peek),迭代Iterator等基本操作。

3、List集合的依赖关系

从List集合的实现类开始,向上溯源,画出List集合的依赖关系:

二、LinkedList结构分析

链表是以节点的方式来存储数据,它是一种线性表,但属于链式存储。而节点是由数据域(data)+ 指针域(next)组成的,链表在内存的空间并不是连续的,每个节点通过指针域来指向下一个节点。链表有带头节点的链表和无头节点的链表之分,也有单向链表和双向链表之分。

Java中的LinkedList是基于链表实现的List集合,它到底是什么样的结构呢?相关的源码分析如下:

public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
    /** LinkedList集合大小 */
    transient int size = 0;
    /** 节点内部类 */
    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<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;
        }
    }

    /** 链表头节点  */
    transient Node<E> first;

    /** 链表尾节点  */
    transient Node<E> last;

    /** LinkedList构造器 */
    public LinkedList() {}
}

从上面代码分析中,我们可以总结出:

  • LinkedList的链表是由节点Node组成的,节点Node内包含了三个变量:元素item、指向上一个节点的引用prev 、指向下一个节点的引用 next,说明LinkedList是一个双向链表结构
  • node()方法用于遍历链表查找出目标节点的操作,比如在指定位置插入元素、删除指定位置上的元素、修改指定位置上的元素、获取元素等操作,当查询的索引index小于链表节点数量的一半时,说明查找的节点位于链表的前半部分,采取从前往后遍历的方式折半查找,反之,采取倒序遍历方式进行折半查找,说明LinkedList的查询效率慢,最差的情况下会遍历链表一半的节点
  • 拥有的无参构造器不初始化成员,说明初始的LinkedList不包含任何节点

三、LinkedList增删与查询操作的性能分析

上面分析了node()方法后发现,LinkedList集合的增删可分为单纯的添加或删除元素操作,比如:public boolean add(E e) {...} 或 public boolean remove(Object o) ,它们不涉及到节点位置的查询;又分为在指定节点位置上进行添加和删除元素操作,比如:public void add(int index, E element) {...} 或 public E remove(int index) {...},因此,为了便于分析LinkedList集合的增删操作和查询操作的性能,这里我尚且将涉及到节点位置查询的操作都视为查询操作了。

LinkedList集合的增删和查询操作,源码分析如下:(注意 —— 这里以List接口内的增删查API为例,而Deque接口内的增删查API,后面再进行对比整理)

public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    /** 链表头节点  */
    transient Node<E> first;

    /** 链表尾节点  */
    transient Node<E> last;

    /** add(E e):向集合中追加一个元素 */
    public boolean add(E e) {linkLast(e); return true;}

    /** add(int index, E element):向集合指定位置插入一个元素,多了几步对索引的检查 */
    public void add(int index, E element) {
        checkPositionIndex(index); // 越界检查

        if (index == size)
            linkLast(element); // 如果index等于集合的大小,追加元素
        else
            linkBefore(element, node(index)); // 否则,根据index查找到该节点后,在该节点附近创建新节点存储元素
    }

    void linkLast(E e) {
        final Node<E> l = last; // 假设存在一个节点
        final Node<E> newNode = new Node<>(l, e, null); // 创建存储该元素e的新节点
        last = newNode; 
        if (l == null)
            first = newNode; // 假设的节点不存在,新节点成了头节点
        else
            l.next = newNode; // 假设的节点存在,该节点的next引用会指向新节点
        size++; // 表示集合数量+1
        modCount++; // 修改统计计数器+1
    }

    /** 在succ节点之前,通过改变节点引用关系创建新的节点,在新节点内存放元素 */
    void linkBefore(E e, Node<E> succ) { 
        // assert succ != null;
        final Node<E> pred = succ.prev; // succ为后节点, pred为前节点
        final Node<E> newNode = new Node<>(pred, e, succ); // 新节点
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

    /** 
     *  remove(Object o):删除集合中的一个元素,
     *       注意 LinkedList集合是可以存储多个null元素的,也可以存储多个相同的元素。 
     **/
   public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) { // 从头开始遍历,找到第一个存在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;
    }

    /** remove(int index):删除指定位置的节点,省去了上面查找具体节点一步 */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index)); // 先查询出要删除的节点,再删除
    }

    /** 删除节点,前提该节点一定存在! */
    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--; // 表示集合数量-1
        modCount++; 
        return element;
    }

    /** 查询集合元素 */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item; // 元素是存放在节点内的item变量上的
    }

    /** 修改集合元素 */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index); // 要修改先找到具体节点
        E oldVal = x.item;
        x.item = element; // 然后替换节点内的元素
        return oldVal;
    }
}

先来看看查询操作的过程,一开始根据查询索引index,调用node()方法确定节点位置,而node()方法内会采用二分法选择不同的遍历查找策略,最坏情况下会遍历链表一半的节点,当LinkedList集合非常大时,查询效率还是比较慢的

再来看看如何添加或删除一个元素的操作,元素是存储在节点内的,因此操作元素一定要通过操作节点实现。如果直接在集合内追加一个元素,只需要将前一节点的next引用指向新节点(前提是集合不为空),即可完成追加操作;如果在集合内的某一位置插入元素,要先找到该位置上对应的节点(势必会调用node()方法遍历查找了,为了方便分析,这里我称为After节点),这样也能确定After节点的前一个节点Before节点,然后创建一个存储插入元素的新节点New节点,最后将Before节点的next引用指向New节点,将After节点的prev引用指向New节点,就能实现断开连接After节点和Before节点,插入New节点。通过改变前后节点与新节点的引用关系,即可完成插入操作;删除的思路类似,本质上都是通过改变节点间引用关系,实现了快速添加或删除元素的功能

前一篇的ArrayList集合源码分析提到过,ArrayList集合基于随机访问接口和数组下标定位元素等特性,在大数据量操作的场景中,查询操作效率远高于增删操作。而LinkedList集合则基于改变节点间的引用关系快速实现增删元素的特点,用在大数据量操作的场景中,增删操作效率远高于查询操作。因此是使用ArrayList集合还是使用LinkedList集合,需要根据实际情况选择。

四、增删改查API对比整理

之前提到的增删改查的源码分析均是基于List接口的,接下来加上Deque接口(利用双端队列的出队和入队,实现集合的头元素和尾元素操作),综合盘点一下:

添加操作

public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    /** 向集合末尾追加一个元素 */
    public boolean add(E e) {linkLast(e);return true;}
    /** 向集合指定位置插入一个元素 */
    public void add(int index, E element) {...}
    /** 求LinkedList集合的并集 */
    public boolean addAll(Collection<? extends E> c) {return addAll(size, c);}
    public boolean addAll(int index, Collection<? extends E> c) {...}

    /** 向集合开头插入一个元素 */
    public void addFirst(E e) {linkFirst(e);}
    /** 向集合末尾追加一个元素,等同于add(E e) */
    public void addLast(E e) {linkLast(e);}
    /** 向集合末尾追加一个元素,等同于add(E e) */
    public boolean offer(E e) {return add(e);}、    
    /** 向集合开头插入一个元素,等同于addFirst(E e) */
    public boolean offerFirst(E e) {addFirst(e);return true;}
    /** 向集合末尾追加一个元素,等同于addLast(E e) */
    public boolean offerLast(E e) {addLast(e);return true;}
    /** 向集合开头插入一个元素,等同于addFirst(E e) */
    public void push(E e) {addFirst(e); }

删除操作

public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    /** 删除集合内的某个元素 */
    public boolean remove(Object o) {...}
    /** 删除指定位置的元素 */
    public E remove(int index) {checkElementIndex(index);return unlink(node(index));}

    /** 删除集合内第一个元素,集合为空,会抛NoSuchElementException */
    public E removeFirst() {final Node<E> f = first; ...; return unlinkFirst(f);}
    /** 删除集合内最后一个元素,集合为空,会抛NoSuchElementException */
    public E removeLast() {final Node<E> l = last; ...; return unlinkLast(l);}
    /** 删除集合内第一个元素,等同于removeFirst() ,集合为空,会抛NoSuchElementException */
    public E remove() {return removeFirst();}
    /** 删除集合内的某个元素,正序遍历后删除第一个出现的元素,等同于remove(Object o) */
    public boolean removeFirstOccurrence(Object o) {return remove(o);}
    /** 删除集合内的某个元素,倒序遍历后删除第一个出现的元素 */
    public boolean removeLastOccurrence(Object o) {...}
    /** 删除集合内第一个元素,集合为空,不会抛异常 */
    public E poll() {final Node<E> f = first;return (f == null) ? null : unlinkFirst(f);}
    /** 删除集合内第一个元素,集合为空,不会抛异常 */
    public E pollFirst() {final Node<E> f = first;return (f == null) ? null : unlinkFirst(f);}
    /** 删除集合内最后一个元素,集合为空,不会抛异常 */
    public E pollLast() {final Node<E> l = last;return (l == null) ? null : unlinkLast(l);}
    /** 删除集合内第一个元素,集合为空,会抛NoSuchElementException */
    public E pop() {return removeFirst();}
}

查询操作

public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    /** 查询集合内指定位置元素 */
    public E get(int index) {checkElementIndex(index);return node(index).item;}

    /** 查询集合内第一个元素,集合为空,会抛NoSuchElementException */
    public E getFirst() {final Node<E> f = first;...;return f.item;}
    /** 查询集合内最后一个元素,集合为空,会抛NoSuchElementException */
    public E getLast() {final Node<E> l = last;...;return l.item;}
    /** 查询集合内第一个元素,集合为空,不抛异常 */
    public E peek() {final Node<E> f = first;return (f == null) ? null : f.item;}
    /** 查询集合内第一个元素,集合为空,不抛异常 */
    public E peekFirst() {final Node<E> f = first;return (f == null) ? null : f.item;}
    /** 查询集合内最后一个元素,集合为空,不抛异常 */
    public E peekLast() {final Node<E> l = last;return (l == null) ? null : l.item;}
    /** 查询集合内第一个元素,集合为空,会抛NoSuchElementException */
    public E element() {return getFirst();}
}

五、迭代器

List集合中有两种迭代器:一种是通过iterator()方法获取的原生迭代器Iterator,另一种是通过listIterator()方法获取的列表迭代器ListIterator,关于二者的比较在ArrayList集合源码分析中也分析过了,可以参考:【JDK源码】ArrayList源码分析

列表迭代器ListIterator

在ArrayList集合中,定义了一个内部类ListItr,该类实现了ListIterator接口,底层通过数组的方式实现了自己的next()、previous()、set()、add()、remove()、forEachRemaining() 等迭代器功能,而LinkedList集合也是如此,只是通过链表方式实现的而已。

原生迭代器Iterator

在ArrayList集合中,定义了一个内部类Itr,该类实现了Iterator接口,底层通过数组的方式只实现了next()、remove()、forEachRemaining() 等迭代器功能,而LinkedList集合则有所不同,定义的内部类DescendingIterator 实现了Iterator接口,这里使用到了适配器设计模式,适配器通过列表迭代器的previous属性,实现了一种降序功能的迭代器DescendingIterator,且只提供了next()、hasNext()、remove()等迭代方法,实现的源码如下:

public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    public Iterator<E> descendingIterator() {
        return new DescendingIterator();
    }

    private class DescendingIterator implements Iterator<E> {
        private final ListItr itr = new ListItr(size());

        public boolean hasNext() {
            return itr.hasPrevious();
        }

        public E next() {
            return itr.previous();
        }

        public void remove() {
            itr.remove();
        }
    }
}

当然不得不提一下ConcurrentModificationException异常,它是foreach遍历时使用集合原生的删除方法造成的,原因在ArrayList集合源码分析中也分析过了,这里提醒一下,使用LinkedList集合进行遍历删除时也要规避这样的问题,即使用迭代器提供的remove()方法。

六、分割器

分割器Spliterator是Java8引入的一种保证并发安全操作集合或Map的接口,集合或Map按照分割规则,分割成多段,在多线程中能得到安全的执行。在ArrayList集合源码分析中,对该接口及其实现类ArrayListSpliterator做了详细而深入的分析,这里不再赘述了,可以参考:【JDK源码】ArrayList源码分析

换汤不换药,LinkedList集合提供的LLSpliterator类,与ArrayListSpliterator类对比后可以看到,API的实现思路是一样的,保证并发安全操作的原理也是一样的,只不过一个是链表方式,一个是数组方式。

小结

至此,LinkedList集合重点的内容基本整理和分析完了,作为List集合的一种类型,有很多地方的特性和ArrayList集合极为相似,同时又有所区别,均在上面进行了对比说明。深入源码分析是不简单的事情,虽然耗费时间,但在这个过程中,确实能发现和收获不少的东西,比如LinkedList的结构是什么样的,为什么说LinkedList集合的增删操作效率高于查询操作,为什么LinkedList集合的降序迭代器能实现降序的功能,为什么LinkedList集合的分割器操作是线程安全的等等,学习的路上贵在坚持,不积硅步无以至千里,点滴付出终将有所收获,共同进步吧~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值