• LinkedList 源码分析


    LinkedList 是一个基于双向链表实现的集合类。
    在这里插入图片描述

    LinkedList 插入和删除元素的时间复杂度

    • 头部插入/删除:只需要修改头结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
    • 尾部插入/删除:只需要修改尾结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
    • 指定位置插入/删除:需要先移动到指定位置,再修改指定节点的指针完成插入/删除,因此需要移动平均 n/2 个元素,时间复杂度为 O(n)。

    LinkedList 实现了以下接口:

    • List : 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
    • Deque :继承自 Queue 接口,具有双端队列的特性,支持从两端插入和删除元素,方便实现栈和队列等数据结构。
    • Cloneable :表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。
    • Serializable : 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便。
      在这里插入图片描述

    每个节点的定义

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    插入元素

    // 在链表尾部插入元素
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
    // 在链表指定位置插入元素
    public void add(int index, E element) {
        // 下标越界检查
        checkPositionIndex(index);
    
        // 判断 index 是不是链表尾部位置
        if (index == size)
            // 如果是就直接调用 linkLast 方法将元素节点插入链表尾部即可
            linkLast(element);
        else
            // 如果不是则调用 linkBefore 方法将其插入指定元素之前
            linkBefore(element, node(index));
    }
    
    // 将元素节点插入到链表尾部
    void linkLast(E e) {
        // 将最后一个元素赋值(引用传递)给节点 l
        final Node<E> l = last;
        // 创建节点,并指定节点前驱为链表尾节点 last,后继引用为空
        final Node<E> newNode = new Node<>(l, e, null);
        // 将 last 引用指向新节点
        last = newNode;
        // 判断尾节点是否为空
        // 如果 l 是null 意味着这是第一次添加元素
        if (l == null)
            // 如果是第一次添加,将first赋值为新节点,此时链表只有一个元素
            first = newNode;
        else
            // 如果不是第一次添加,将新节点赋值给l(添加前的最后一个元素)的next
            l.next = newNode;
        size++;
        modCount++;
    }
    
    // 在指定元素之前插入元素
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;断言 succ不为 null
        // 定义一个节点元素保存 succ 的 prev 引用,也就是它的前一节点信息
        final Node<E> pred = succ.prev;
        // 初始化节点,并指明前驱和后继节点
        final Node<E> newNode = new Node<>(pred, e, succ);
        // 将 succ 节点前驱引用 prev 指向新节点
        succ.prev = newNode;
        // 判断尾节点是否为空,为空表示当前链表还没有节点
        if (pred == null)
            first = newNode;
        else
            // succ 节点前驱的后继引用指向新节点
            pred.next = newNode;
        size++;
        modCount++;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    获取元素

    // 获取链表的第一个元素
    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;
    }
    
    // 获取链表指定位置的元素
    public E get(int index) {
      // 下标越界检查,如果越界就抛异常
      checkElementIndex(index);
      // 返回链表中对应下标的元素
      return node(index).item;
    }
    
    // 返回指定下标的非空节点
    Node<E> node(int index) {
        // 断言下标未越界
        // assert isElementIndex(index);
        // 如果index小于size的二分之一  从前开始查找(向后查找)  反之向前查找
        if (index < (size >> 1)) {
            Node<E> x = first;
            // 遍历,循环向后查找,直至 i == index
            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;
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    get(int index) 或 remove(int index) 等方法内部都调用了node(int index)方法来获取对应的节点。
    从这个方法的源码可以看出,该方法通过比较索引值与链表 size 的一半大小来确定从链表头还是尾开始遍历。如果索引值小于 size 的一半,就从链表头开始遍历,反之从链表尾开始遍历。这样可以在较短的时间内找到目标节点,充分利用了双向链表的特性来提高效率

    删除元素

    // 删除并返回链表的第一个元素
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    
    // 删除并返回链表的最后一个元素
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    
    // 删除链表中首次出现的指定元素,如果不存在该元素则返回 fals
    public boolean remove(Object o) {
        // 如果指定元素为 null,遍历链表找到第一个为 null 的元素进行删除
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            // 如果不为 null ,遍历链表找到要删除的节点
            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));
    }
    
    E unlink(Node<E> x) {
        // 断言 x 不为 null
        // 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 { // 如果前一个节点不为空
            // 将前一个节点的 next 指针指向当前节点的下一个节点
            prev.next = next;
            // 将当前节点的 prev 指针置为 null,,方便 GC 回收
            x.prev = null;
        }
    
        // 如果下一个节点为空,则说明当前节点是尾节点
        if (next == null) {
            // 直接让链表尾指向当前节点的前一个节点
            last = prev;
        } else { // 如果下一个节点不为空
            // 将下一个节点的 prev 指针指向当前节点的前一个节点
            next.prev = prev;
            // 将当前节点的 next 指针置为 null,方便 GC 回收
            x.next = null;
        }
    
        // 将当前节点元素置为 null,方便 GC 回收
        x.item = null;
        size--;
        modCount++;
        return element;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    unlink图解
    在这里插入图片描述

    遍历链表

    推荐使用for-each 循环来遍历 LinkedList 中的元素, for-each 循环最终会转换成迭代器形式。

    作者声明

    如有问题,欢迎指正!
    
    • 1
  • 相关阅读:
    详解模板引擎一
    SV-线程的使用与控制
    Map 和 WeakMap:JavaScript 中的键值对集合
    Java面向对象的特点之:继承
    html5盒子模型
    线性变换与矩阵(3Blue1Brown视频笔记)
    java毕业设计基于网络平台个人博客系统Mybatis+系统+数据库+调试部署
    【李宏毅】注意力机制+transformer详解
    Ubantu GoLand安装
    springboot自写插件封包
  • 原文地址:https://blog.csdn.net/weixin_45247019/article/details/132811794