详解LinkedList中的底层实现

1.LinkedList的构造器

  • 无参构造器
/**
 * Constructs an empty list.
 */
public LinkedList() {
}
  • 构造Collection: 只要是 Collection 下的实现类都可以被 LinkedList 构造

// ? extends E: 只要是E的子类及E类型的类都可以使用

public LinkedList(Collection<? extends E> c) {
    // 调用无参构造器
    this();
    // 把集合 c 中的所有元素都添加到 LinkedList 的对象中
    addAll(c);
}

2.添加元素

  • 头插: 把元素插入到链表的开头
public void addFirst(E e) {
    linkFirst(e);
}

// 进行头插
private void linkFirst(E e) {
    // f 指向了头节点
    final Node<E> f = first;
    // 把 newNode 连接起来, newNode.next = f;
    final Node<E> newNode = new Node<>(null, e, f);
    // 让first 重新指向头节点
    first = newNode;
    // 判空
    if (f == null)
        last = newNode;
    else
        f.prev = newNode; //连接起来后面的节点
    // 长度自增
    size++;
    modCount++;
}
  • 尾插: 把元素添加到链表的最后
public boolean add(E e) {
    linkLast(e);
    return true;
}

// 真正进行尾插的地方
void linkLast(E e) {
    // l 指向 last, 即链表的最后一个位置
    final Node<E> l = last;
    // newNode 绑定了 prev, 即 newNode.prev = l;
    final Node<E> newNode = new Node<>(l, e, null);
    // 更新尾结点; last 为空, 则更新 newNode 为尾结点
    last = newNode;
    // l 为空, 则说明链表中一个节点都没有
    if (l == null)
        first = newNode; // 更新为头节点
    else
        l.next = newNode; // 把 l.next 指向 newNode;
    size++; // 链表长度 +1
    modCount++;
}
  • 指定位置添加元素
public void add(int index, E element) {
    // 判断 index 是否合理
    checkPositionIndex(index);

    // index 为链表的长度
    if (index == size)
        linkLast(element); // 尾插
    else
        // node(index) 找到要插入的位置, 然后进行插入
        linkBefore(element, node(index));
}

// succ 为要插入的位置, e 为要插入的值
// 尾插上面代码已经体现, 还要头插和任意位置插入未体现
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    // pred 为插入位置的前一个
    final Node<E> pred = succ.prev;
    // 绑定 newNode.prev = pred, newNode.next = succ;
    final Node<E> newNode = new Node<>(pred, e, succ);
    
    // 前面已经知道了 newNode 的 prev 和 next, 还差 pred.next 和 succ.prev
    // 这个地方是绑定 succ.prev
    succ.prev = newNode;
    // 说明要插入的节点是头节点
    if (pred == null)
        first = newNode; // 头插
    else
        // 任意位置, 绑定 pred.next
        pred.next = newNode;
    size++; // 链表长度 +1
    modCount++;
}
  • 指定位置添加另一个集合中的所有元素
public boolean addAll(Collection<? extends E> c) {
    // 从 0 位置开始把集合 c 中的元素全部添加到 LinkedList 的对象中
    return addAll(size, c);
}

// 具体实现细节
public boolean addAll(int index, Collection<? extends E> c) {
    // 检查下标是否正常
    checkPositionIndex(index);
    
    // 把集合 c 转化成 Object 数组
    Object[] a = c.toArray();
    // 获取数组长度
    int numNew = a.length;
    // 空数组, 直接返回, 无需构造
    if (numNew == 0)
        return false;
    
    // 定义前驱和后继节点
    Node<E> pred, succ;
    // index 的值和 size 相同, 则需要尾插(? 因为 size 代表当前 LinkedList 对象的元素个数)
    if (index == size) {
        succ = null; 
        pred = last; // pred 指向最后一个元素
    } else {
        // succ 指向要插入位置的节点
        succ = node(index);
        // pred 指向要插入位置节点的前驱节点
        pred = succ.prev;
    }
    
    // 循环遍历数组 a
    // 要插入的所有值, 在 pred 的后面插入, 在 succ 的前面插入
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o; // 强制类型转化, 把 Object 转为特定类型
        // 进行插入, 让 newNode.prev 指向 pred, newNode.next = null;
        Node<E> newNode = new Node<>(pred, e, null);
        // 若 pred 为空, 则说明 succ 是头节点, 即说明前面是进入了 else 语句
        if (pred == null)
            first = newNode; // 更新头节点
        else
            pred.next = newNode; // 让 pred 和 newNode 相互连接(串起来)
        pred = newNode; // 更新节点, 继续插入
    }
    
    // succ 为空, 则说明是尾插法, 即进入了 size == index 内部
    if (succ == null) {
        // last 指向最后一个元素
        last = pred;
    } else {
        // 连接 pred 和 succ 节点, 把链表串起来
        pred.next = succ;
        succ.prev = pred;
    }
    
    // 添加长度
    size += numNew;
    modCount++;
    return true;
}

3.删除元素

  • 头删: 把链表的最后一个元素删掉
public E removeFirst() {
    final Node<E> f = first;
    // 链表中一个节点也没有
    if (f == null)
        throw new NoSuchElementException();
    // unlinkFirst 方法中把头节点传过去
    return unlinkFirst(f);
}

// 进行头删
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    // 获取待删除节点的值
    final E element = f.item;
    // next 指向待删除节点的下一个节点
    final Node<E> next = f.next;
    // 把头节点的值置为 null
    f.item = null;
    // 让 f(头节点)和后面的节点断开
    f.next = null; // help GC
    // 头节点重置
    first = next;
    // 说明 f 只有一个节点的情况
    if (next == null)
        last = null;
    else
        next.prev = null; // 把 next 的前驱也置为空, 彻底和 f 断开连接
    size--;
    modCount++;
    // 返回删除头节点的值
    return element;
}
  • 尾删: 把链表的最后一个元素删掉
public E removeLast() {
    final Node<E> l = last;
    // 一个节点都不存在
    if (l == null)
        throw new NoSuchElementException();
    // 把尾结点传到 unlinkLast 方法中
    return unlinkLast(l);
}

// 进行尾删
private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    // 获取待删除节点的值
    final E element = l.item;
    // 获取待删除节点的前一个节点
    final Node<E> prev = l.prev;
    
    // 把待删除节点的值域和 next 域均置为空
    l.item = null;
    l.prev = null; // help GC
    // 把尾结点重置到待删除节点的前一个节点
    last = prev;
    // 只有一个节点的情况, 则 prev 为空, 则需要把头节点也置为空
    if (prev == null)
        first = null;
    else
        // 删掉最后一个节点, 即和链表前面的链表断开连接
        prev.next = null;
    size--;
    modCount++;
    return element;
}
  • 删除指定节点: 那个节点值和传入要删除的值相同, 则删除该节点
// 删掉节点值与 o 相等的节点
public boolean remove(Object o) {
    // 判空
    if (o == null) {
        // 从头开始遍历节点
        for (Node<E> x = first; x != null; x = x.next) {
            // 判断那个的节点值为 null, 则删除这个节点
            if (x.item == null) {
                // 待删除的节点传到 unlink 中
                unlink(x);
                return true;
            }
        }
    } else { // 给的值不为 null
        // 遍历链表
        for (Node<E> x = first; x != null; x = x.next) {
            // 相等则删除
            if (o.equals(x.item)) {
                // 待删除的节点传到 unlink 中
                unlink(x);
                return true;
            }
        }
    }
    // 没有找到有相同的值
    return false;
}

// 删除节点的逻辑
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 节点
    // 把x节点的值置为空
    x.item = null;
    size--;
    modCount++;
    return element;
}

 

4.获取/修改元素

  • 获取index处的节点值
public E get(int index) {
    // 判断下标是否合法
    checkElementIndex(index);
    // 获取 index 处的值后, 返回
    return node(index).item;
}

// 查找 index 处的节点值
Node<E> node(int index) {
    // assert isElementIndex(index);
    
    // 判断 index 是否在 size 的 1/2 内, 这个设计的目的是提高查找效率
    if (index < (size >> 1)) {
        // 定义查询节点
        Node<E> x = first;
        // 从头节点开始进行遍历, 一直到 index 处的节点位置
        for (int i = 0; i < index; i++)
            x = x.next;
        // 返回 index 处的节点的位置
        return x;
    } else {
        // 查找的节点在后半段
        Node<E> x = last;
        // 从尾结点向前遍历
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        // 返回 index 处的节点
        return x;
    }
}
  • 修改index处节点的节点值
public E set(int index, E element) {
    // 判断下标是否合法
    checkElementIndex(index);
    // 获取 index 处的节点值
    Node<E> x = node(index);
    // 获取原来节点的节点值
    E oldVal = x.item;
    // 把节点的节点值进行更新
    x.item = element;
    // 返回原先的节点值
    return oldVal;
}

 

5.获取元素第一次/最后一次出现的位置

  • 获取元素第一次出现的位置
public int indexOf(Object o) {
    // 计数器, 用于获取目标节点处于那个下标
    int index = 0;
   
    if (o == null) {
        // 若是 o 为空, 也需要找到 null 在链表中的那个位置, 把这个位置返回
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        // 不为空, 那么就判断那个节点值和当前的 o 相等, 相等则返回对应的下标
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    // 没有找到的情况
    return -1;
}
  • 获取元素最后一次出现的位置
public int lastIndexOf(Object o) {
    // 计数器, 下标从链表的最后一个位置开始
    int index = size;

    if (o == null) {
        // 若是 o 为空, 也需要找到 null 在链表中的那个位置, 把这个位置返回
        // 从尾向头开始进行遍历
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        // 不为空, 那么就判断那个节点值和当前的 o 相等, 相等则返回对应的下标
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    // 链表中不存在对应的值
    return -1;
}

 

6.链表长度

public int size() {
    return size;
}

// size 是 LinkedList 中的一个计数器, 用于记录链表长度的
transient int size = 0;

 

7.清空链表

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
    // 遍历链表, 从头开始一个一个释放所占空间
    for (Node<E> x = first; x != null; ) {
        // 记录 x 的下一个节点
        Node<E> next = x.next;
        // 把 x 节点释放
        x.item = null;
        x.next = null;
        x.prev = null;
        // 指向下一个节点
        x = next;
    }
    // 头节点和尾结点置为 null
    first = last = null;
    // 长度置为 0
    size = 0;
    modCount++;
}

<think>我们正在讨论JavaLinkedList底层实现原理,重点包括数据结构、源码分析和关键操作。根据引用[1]和[2]:LinkedList是一个双向链表,每个节点(Node)包含数据项(item)和两个指针(prev和next)分别指向前一个节点和后一个节点。引用[2]提供了LinkedList类的关键属性:size(节点个数)、first(指向头节点)和last(指向尾节点)。引用[5]详细描述了add方法(实际调用linkLast方法)在链表尾部添加元素的过程。我们将从以下几个方面展开:1.节点结构2.初始化3.添加元素(以add方法为例)4.关键操作的时间复杂度5.与ArrayList的对比注意:在回答中,我们需要引用所给的引用编号,例如在描述add方法时引用[5],在描述属性时引用[2]。同时,由于用户要求生成相关问题,我们将在最后提出相关问题。</think>### Java LinkedList底层实现原理详解 #### 1. **核心数据结构:双向链表** LinkedList底层使用**双向链表**结构实现,每个节点包含三部分: ```java private static class Node<E> { E item; // 存储数据元素 Node<E> next; // 指向后继节点 Node<E> prev; // 指向前驱节点 } ``` - 节点通过`prev`和`next`指针互相连接 - 链表两端由`first`和`last`指针标记(引用[2]) - 初始化时`first = last = null`(引用[1]) #### 2. **关键属性分析(源码级)** ```java public class LinkedList<E> { transient int size = 0; // 链表节点数量 transient Node<E> first; // 头节点指针 transient Node<E> last; // 尾节点指针 } ``` - `transient`修饰符表示这些属性不参与序列化 - 维护`size`实现$O(1)$时间的`size()`方法(引用[2]) #### 3. **添加元素原理(以add()为例)** ```mermaid graph LR A[add(e)] --> B[linkLast(e)] B --> C[创建新节点] C --> D[调整指针] D --> E[更新边界] ``` **源码关键步骤(引用[5]):** 1. 将尾指针暂存:`Node<E> l = last` 2. 创建新节点:`new Node<>(l, e, null)` - `prev = l`(当前尾节点) - `item = e`(新元素) - `next = null`(尾部无后继) 3. 更新尾指针:`last = newNode` 4. 边界处理: ```java if (l == null) // 原链表为空 first = newNode; else // 原链表非空 l.next = newNode; ``` 5. 更新`size`和修改计数 #### 4. **操作时间复杂度对比** | 操作 | 时间复杂度 | 实现原理 | |--------------|------------|------------------------------| | `add(E)` | $O(1)$ | 直接修改尾节点指针(引用[5])| | `add(int,E)` | $O(n)$ | 需遍历到指定位置 | | `get(int)` | $O(n)$ | 必须从头遍历 | | `remove()` | $O(1)$ | 直接调整相邻节点指针 | #### 5. **线程安全性** - **非线程安全**:默认实现未同步(引用[3]) - 线程安全方案: ```java // 使用Collections同步包装 List<String> safeList = Collections.synchronizedList(new LinkedList<>()); ``` - 或使用`ConcurrentLinkedDeque`(并发安全链表) #### 6. **与ArrayList核心差异** | 特性 | LinkedList | ArrayList | |--------------|---------------------|--------------------| | 内存结构 | 指针链接的非连续节点| 连续内存数组 | | 随机访问 | $O(n)$ | $O(1)$ | | 头部插入 | $O(1)$ | $O(n)$ | | 迭代器 | 双向遍历 | 单向遍历 | | 内存占用 | 更高(每个节点24B) | 更低(仅数据存储) | > **适用场景建议**:优先选择LinkedList当需要: > 1. 频繁在**头部/中部插入删除** > 2. 不需要随机访问 > 3. 需要实现队列/双端队列(实现`Deque`接口) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值