LinkedList源码解析(JDK1.8)

LinkedList源码解析(JDK1.8)

LinkedList是基于链表实现的双向链表

同样我们还是先上类的关系图

LinkedList类的关系图

从类的关系图中可以看出LinkedList继承一个抽象类和实现了四个接口,然后分别简单介绍一下:

  • AbstractSequentialList:这里主要提供iterator迭代器的相关操作
  • List:提供链表的增删改查、迭代器遍历等操作
  • Deque:提供队首、队尾增删改查等操作
  • Cloneable:按字段复制操作
  • Serializable:启用其序列化功能操作

属性

属性相关的源码


   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;

复制代码

transient int size = 0;//链表中存放元素的数量

transient Node first;//头节点

transient Node last;//尾节点

构造方法



    /**
     * 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():无参构造方法 _ LinkedList(Collection<? extends E> c):集合c直接设置链表顺序的构造方法

节点

    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;
        }
    }
复制代码

其中element表示存储的Value值,pre、next分别指向当前节点的上一个节点和下一个节点,由此也可以看出来是双向的

增删改查

我们实际项目中,围绕LinkedList做的最多的就是增删改查相关操作,我们将细细的品一品官方的实现方式

拆入元素

单个元素拆入

拆入元素先介绍最简单的单个拆入元素

    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
    void linkLast(E e) {
        //得到当前的最后一个元素节点
        final Node<E> l = last;
        //构造新节点,并设置当前上一个节点是l,元素为e,下一个节点为null
        final Node<E> newNode = new Node<>(l, e, null);
        //现在最新的last节点为新插入的节点
        last = newNode;
        //判断l节点是否为空,为空的话则代表当前插入的是第一个元素,则需要设置首节点也为当前的新节点,反之则设置原先的最后一个节点的next节点是当前新的节点
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        //元素数量+1    
        size++;
        //链表改动次数+1
        modCount++;
    }
复制代码

从上面可以看出,直接add(e)是非常简单的,我们接下来来看一下其他的拆入元素的方法

下标位置拆入节点


    public void add(int index, E element) {
        //下标拆入都先判断下标是否越界,越界就抛出异常
        checkPositionIndex(index);
        //如果拆入位置是尾部index,和上面的直接拆入元素调用的同一个方法就不分析来,来看看linkBefore的拆入
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    
    //succ拿的来当前index下标的节点
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        //得到index下标节点的前一个
        final Node<E> pred = succ.prev;
        //构造新节点,新节点的前一个节点为原先这个index位置的前一个节点,设置新节点的下一个节点为原先index节点
        final Node<E> newNode = new Node<>(pred, e, succ);
        //设置原先index位置的节点的前一个为新节点
        succ.prev = newNode;
        //如果pred节点为null,则代表succ是头节点,现在设置头节点为新节点
        if (pred == null)
            first = newNode;
        else
            //如果pred节点不为null,则修改pred的下一个节点为新节点
            pred.next = newNode;
        //元素数量+1    
        size++;
        //链表修改次数+1
        modCount++;
    }    
    
复制代码

addFirst()、addLast()也是新简单的,源码进去看一下就行,我们来讲解一下addAll()方法

多个元素拆入


    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;
        //拆入位置是size,即接着尾部拆入
        if (index == size) {
            //当前节点没有,前一个节点就是当前最后一个节点
            succ = null;
            pred = last;
        } else {
            //当前节点就是index位置的节点,前一个节点直接拿prev获取
            succ = node(index);
            pred = succ.prev;
        }
        //快速for处理待拆入元素的数组
        for (Object o : a) {
            //元素转换
            @SuppressWarnings("unchecked") E e = (E) o;
            //构建新节点的前一个是pred,元素为e,下一个节点为null
            Node<E> newNode = new Node<>(pred, e, null);
            //如果pred节点为null,则代表原先index位置的节点为头节点,则需要重新设置头节点
            if (pred == null)
                first = newNode;
            else
                //如果pred节点不为null,则设置原先index位置的节点的下一个节点为当前新节点
                pred.next = newNode;
            //一个新节点完成后,设置上一个为当前新节点,直到把所有元素拆入完成    
            pred = newNode;
        }
        //index位置的原先节点为null,则代表index原先的前一个节点就是最后一个节点,则设置最后一个节点就是最新最后的那个节点
        if (succ == null) {
            last = pred;
        } else {
            //index位置的原先节点不为null,则设置拆入数组的最后一个元素的最后一个节点的next是succ,succ的前一个是当前这个
            pred.next = succ;
            succ.prev = pred;
        }
        //得到新链表元素的数量
        size += numNew;
        //链表改动次数+1
        modCount++;
        return true;
    }

复制代码

那直接addAll(Collection<? extends E> c)就更不用说来,从中可以看出LinkedList是没有长度限制的,LinkedList的拆入和ArrayList的拆入相比更快速

删除元素

单个元素删除


    public boolean remove(Object o) {
        //删除的元素为null
        if (o == null) {
            //从头节点开始查找,如果item相等则进行unlink操作删除这个节点
            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;
    }
    
    //x为需要删除的节点
    E unlink(Node<E> x) {
        // assert x != null;
        //得到x节点元素
        final E element = x.item;
        //拿到前一个节点,后一个节点
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        //前一个节点为null,则代表x为头节点
        if (prev == null) {
            //设置新的头节点
            first = next;
        } else {
            //设置prex.next为x.next
            prev.next = next;
            //x的前一个节点为null
            x.prev = null;
        }
        //如果后一个节点为null,则代表x为尾节点
        if (next == null) {
            //设置新的尾节点
            last = prev;
        } else {
            //设置x.next.prev为x.prev
            next.prev = prev;
            //x的后一个节点为null
            x.next = null;
        }
        //设置x节点的元素为null,帮助gc回收
        x.item = null;
        //链表元素-1
        size--;
        //改动次数+1
        modCount++;
        return element;
    }    

复制代码

直接删除某个元素讲完来,我们来讲一下根据index下标删除元素

    public E remove(int index) {
        //下标都先判断下标是否越界,越界就抛出异常
        checkElementIndex(index);
        return unlink(node(index));
    }
    
    Node<E> node(int index) {
        // assert isElementIndex(index);
        //二分查找的思想,判断一下是在链表的前半部分还是后半部分
        if (index < (size >> 1)) {
            //从头节点一直找到index位置拿到节点
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            //从尾节点一直找到index位置拿到节点
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    
复制代码

remove(int index)也是很好理解的,remove()和removeFirst()都是从头节点开始删除,removeLast()删除尾节点

更新元素

    public E set(int index, E element) {
        //只要有index,都先检验是否越界
        checkElementIndex(index);
        //node上面分析过就是为了找到index位置的节点
        Node<E> x = node(index);
        //设置新节点的元素,并返回原先的老元素
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
复制代码

查找元素

get()里面的方法前面都介绍过了

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    
复制代码

整体看来LinkedList的实现也是很简单的,我们可以看出来LinkedList增删毕竟快,改查比较慢,我们和ArrayList对比分析便于理解,两者的增删改查的快慢完全相反的。因此也可以得出在实际项目中,我们需要频繁增删数据的用LinkedList,我们需要频繁改查数据的用ArrayList

其他方法

像contains()、clear()这几个我们也来简单的看一下

    public boolean contains(Object o) {
        return indexOf(o) != -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;
    }
    
    和上面分析过的node()方法及其相似
    
    
    
    public void clear() {
        // Clearing all of the links between nodes is "unnecessary", but:
        // - helps a generational GC if the discarded nodes inhabit
        //   more than one generation
        // - is sure to free memory even if there is a reachable Iterator
        //头节点开始循环,设置元素,前一个节点、后一个节点都为null
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
    }
        
复制代码

线程安全问题

  • LinkedList是线程不安全的
  • Collections.synchronizedList()可实现LinkedList的线程安全
  • ConcurrentLinkedDeque是线程安全的

我们要对比上面的这些相关的来便于我们理解和分析,然后还需要结合ArrayList进行对比分析

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享