• LinkedList使用及原理


    一、前言

    前面介绍了ArryList的原理和使用方法,本篇来介绍List家族中另一个重要成员——LinkedList。LinkedList和ArrayList一样是集合List的实现类,虽然较之ArrayList,其使用场景并不多,但同样有用到的时候,那么接下来,我们来认识一下它。

    二、LinkedList的继承关系

    通过IDEA生成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;
    
    
    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类的主要属性有size,first和last。其中,size标识节点的个数,first指向链表的头节点,last指向链表的尾节点。再看一下节点的定义,也就是Node类,可以看出,LinkedList其实就是一个双向链表。

    四、LinkedList的构造函数

    LinkedList的提供两个构造函数。一个无参构造函数,一个参数未集合构造函数。

    4.1 无参构造函数

    无参构造函数很简单,只是创建了一个LinkedList的对象。

    public LinkedList() {
    }

    4.2 有参构造函数

    有参构造函数首先调用无参构造函数新建一个对象,然后再调用addAll将集合添加进去。

    public LinkedList(Collection<? extends E> c) {
            this();
            addAll(c);
    }

     再来看一下addAll方法,我将相关的方法都粘了过来,逐一进行分析public boolean addAll(Collection<? extends E> c)         return addAll(size, c);

    }
    
    
    public boolean addAll(int index, Collection<? extends E> c) {
            checkPositionIndex(index);
    
            Object[] a = c.toArray();
            int numNew = a.length;
            if (numNew == 0)
                return false;
    //pred指向插入位置的前一个节点,succ指向插入位置的后一个节点
            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;
    //新建一个节点,前驱为prep所指向的节点 Node
    <E> newNode = new Node<>(pred, e, null); if (pred == null)
    //新创建的对象,这里会执行 first
    = newNode; else
    //pred的后置节点为新建节点 pred.next = newNode;
    //将pred前移 pred
    = newNode; } if (succ == null) {
    //设置尾节点 last
    = pred; } else {
    //指向后一个节点 pred.next
    = succ; succ.prev = pred; } size += numNew; modCount++; return true; } private void checkPositionIndex(int index) { if (!isPositionIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private boolean isPositionIndex(int index) { return index >= 0 && index <= size; }

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

     首先调用addAll的重载方法,可以再某个位置添加节点,传入size,此时size的值为0。首先调用 checkPositionIndex(index)方法,然后将要得到要插入位置的前置节点和后置节点,最后插入元素,详细看代码注释。

    五、常用方法解读

    这里只说一下插入和删除,他们牵涉到引用的变动

     5.1 添加元素

    LinkedList主要提供addFirstaddLastaddaddAll等方法来实现元素的添加。下面我们一一来看:public void addFirst(E e) {        linkFirst(e);

    }
    
    
    private void linkFirst(E e) {
    //保存第一个元素
    final Node<E> f = first;
    //创建新元素,并将后驱指向原来的first
    final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) last = newNode; else
    //原来的前置节点前驱为新节点
    f.prev = newNode; size++; modCount++; }

    add方法和addLast方法最后调用的都是一个方法,总体思路一样,只是牵涉到引用的指向。

    public boolean add(E e) {
            linkLast(e);
            return true;
    }
    
    public void addLast(E e) {
            linkLast(e);
    }
    
    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++;
     }

     看了这几个添加,通过下面这个图来看一下吧

     5.2 移除元素

    这里只说一下public E 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) {
    //前置为空,头节点为next 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; }

    通过下面这张图就可以清楚的标识引用的变动了

     六、总结

    本篇对LinkedList的原理进行了分析,希望对大家有所帮助,接下来会对比一下ArrayList和LinkedList,希望小伙伴们持续关注。

     

  • 相关阅读:
    Linux下查看tomcat版本
    React跳转路由传参3种方法和区别
    pc端布局方案
    vue针对搜索引擎做SEO优化
    vue-cli3配置vue.config.js持续更新
    MySQL连接语法(多表查询)
    MySQL约束和外键约束
    MySQL数据库查询
    MySQL数据库入门
    jQuery用法和bootstrap框架
  • 原文地址:https://www.cnblogs.com/ChenBingJie123/p/12660880.html
Copyright © 2020-2023  润新知