图解LinkedList底层原理

发布于:2024-12-18 ⋅ 阅读:(26) ⋅ 点赞:(0)

图解LinkedList底层原理


本篇将讲解Java中的一个集合LinkedList的底层实现原理,会查看并分析底层源码,结合图解的方式,理解其添加数据的过程

数据结构

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;
    }
}
  • 每个节点存储当前元素的值(item)及指向前后节点的引用(prevnext
  • LinkedList 是一个双向链表,支持从头部或尾部高效地插入和删除
First
Node 1
prev: null
item
next: Node2
Node 2
prev: Node1
item
next: Node3
Node 3
prev: Node2
item
next: Last
Last

成员变量

LinkedList 的核心成员变量如下:

transient int size = 0;      // 链表中元素的数量
transient Node<E> first;     // 链表的头节点
transient Node<E> last;      // 链表的尾节点
  • size:记录链表中元素的数量
  • first:指向链表的头节点
  • last:指向链表的尾节点

构造方法

LinkedList 提供了默认的无参构造方法:

public LinkedList() {}
  • LinkedList 初始化时,firstlast 都为 null,表示链表为空。

add 方法源码分析

当我们调用 add(E e) 方法时,例如:

LinkedList<String> list = new LinkedList<>();
list.add("Hello");

以下是 add 方法的源码:

public boolean add(E e) {
    linkLast(e);
    return true;
}
  • add 方法通过调用 linkLast(E e) 方法,将新元素添加到链表尾部

linkLast 方法解析

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;    // 更新旧尾节点的 next 引用
    size++;                  // 链表长度加 1
    modCount++;              // 修改计数器
}

当我add第一个元素e时,他的插入过程如下:

  1. 新节点创建

    final Node<E> l = last;  // 获取当前尾节点
    final Node<E> newNode = new Node<>(l, e, null)
    last = newNode;          // 更新尾节点
    

    通过 Node 构造方法,将新节点的 prev 指向当前尾节点,当前尾节点为null,所以prev指向null,next 指向 null,再将尾节点last指向新节点

    First
    Node 1
    prev: null
    item:e
    next:null
    Last
  2. 更新链表结构

    if (l == null)           // 如果链表为空
            first = newNode;     // 新节点即为头节点
        else
            l.next = newNode;
    
    • 如果链表为空(l == null),更新头节点 first
    • 如果链表非空,将当前尾节点的 next 指向新节点

    此时的l是未更新之前的last,为null,所以更新头节点也指向Node 1

    First
    Node 1
    prev: null
    item:e
    next:null
    Last
  3. 更新尾节点和链表长度:新节点成为尾节点,size 自增


接着我们再add第二个元素e2,同样的步骤再次执行:

  1. 新节点创建

    final Node<E> l = last;  // 获取当前尾节点
    final Node<E> newNode = new Node<>(l, e, null)
    last = newNode;          // 更新尾节点
    

    在添加第一个元素时,尾节点更新为了Node 1,此时的把尾节点赋值给ll的值为Node 1

    创建一个新节点传入了l因此新节点的prev指向Node 1,next为null,并且更新尾节点,把尾节点指向新节点,此时last的值就为Node 2了

    First
    Node 1
    prev: null
    item: e
    next: null
    Node 2
    prev: Node 1
    item: e2
    next: null
    Last
  2. 更新链表结构:

    if (l == null)           // 如果链表为空
            first = newNode;     // 新节点即为头节点
        else
            l.next = newNode;
    

    此时的l早已不是null了,而是Node1,因此走else的逻辑,将l, 也就是Node 1的next指向新节点,形成一个双向指针

    First
    Node 1
    prev: null
    item: e
    next: Node 2
    Node 2
    prev: Node 1
    item: e2
    next: null
    Last
  3. 更新尾节点和链表长度:新节点成为尾节点,size 自增

此时双向链表的雏形已经形成


接着我们再次add一个新元素e3,依旧是同样的操作:

  1. 新节点创建

    final Node<E> l = last;  // 获取当前尾节点
    final Node<E> newNode = new Node<>(l, e, null)
    last = newNode;          // 更新尾节点
    

    经过e2的插入过程,last此时为Node2,创建新节点时,其prev就为Node2,last 也更新为了Node3

    First
    Node 1
    prev: null
    item: e
    next: Node 2
    Node 2
    prev: Node 1
    item: e2
    next: null
    Node 3
    prev: Node 2
    item: e3
    next: null
    Last
  2. 更新链表结构:

    if (l == null)           // 如果链表为空
            first = newNode;     // 新节点即为头节点
        else
            l.next = newNode;
    

    依旧执行else,将Node 2 的next指向Node 3,形成双向链表:

    First
    Node 1
    prev: null
    item: e
    next: Node 2
    Node 2
    prev: Node 1
    item: e2
    next: Node 3
    Node 3
    prev: Node 2
    item: e3
    next: null
    Last

linkBefore 方法

void linkBefore(E e, Node<E> succ) {
    final Node<E> pred = succ.prev; // 获取前驱节点
    final Node<E> newNode = new Node<>(pred, e, succ); // 创建新节点
    succ.prev = newNode; // 更新后继节点的 prev
    if (pred == null)    // 如果插入位置为头部
        first = newNode;
    else
        pred.next = newNode; // 更新前驱节点的 next
    size++;
    modCount++;
}

该方法与尾插法类似,节点的插入位置变成了从头节点位置插入

具体流程:

  1. 确定插入位置的前驱和后继节点
  2. 更新新节点的 prevnext 引用,同时修改前驱和后继节点的指针
  3. 如果插入位置为链表头部或尾部,更新 firstlast 节点

插入到指定位置

add(int index, E element)

add 方法支持在链表的任意位置插入元素:

public void add(int index, E element) {
    checkPositionIndex(index); // 检查索引范围
    if (index == size) // 如果插入到链表尾部
        linkLast(element);
    else
        linkBefore(element, node(index));
}

关键逻辑

  • 边界判断:通过 checkPositionIndex() 检查索引是否越界
  • 插入方式:
    • 如果插入位置为链表尾部,调用 linkLast 方法
    • 如果插入位置在中间,调用 linkBefore 方法

网站公告

今日签到

点亮在社区的每一天
去签到