集合节点保存的都是对象的引用,而非具体值,文中案例仅仅为了方便实现原理的演示。

1. 底层数据结构

LinkedList 基于 双向链表 实现,内部通过 Node<E> 节点相互连接:

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 类中,通过以下字段维护链表头尾:

transient Node<E> first; // 链表的头节点引用
transient Node<E> last; // 链表的尾节点引用

first 始终指向第一个节点,last 始终指向最后一个节点。

双向链表支持从头或尾进行 快速插入/删除。但不能像数组一样,根据索引下标进行地址计算,所以不支持 随机访问,通过索引访问需要 遍历节点。

1.1. LinkedList 特性

  • 顺序存储:维护插入顺序,但本质为链式结构;

  • 节点插入/删除:在任意位置插入或删除均为 O(1)(找到节点后);

  • 访问效率:按索引访问需 O(n),因为需要从头或尾遍历;

  • 支持 null 元素,保存的是对象引用。

2. 元素插入(增)

LinkedList 提供四种常用插入方式:

方法 说明
add(E e) 在末尾插入
add(int index, E element) 在指定位置插入
addFirst(E e) / offerFirst(E e) 在头部插入
addLast(E e) / offerLast(E e) 在尾部插入

2.1. linkFirst和linkLast 核心源码

link指的是连接节点的意思,firstlast分别表示从头部连接(插入)节点和从尾部连接(插入)节点。所以,在学习插入元素时,需要相对这两块源码熟悉。

private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
} private 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++;
}
  • 创建新节点并调整前后指针;

  • 首次插入时 firstlast 同时指向该节点;

  • size++modCount++ 支持 fail-fast。

尾部插入调用linkLast方法即可,源码如下

public void addLast(E e) {
linkLast(e);
}

尾部插入动画效果

头部插入调用linkFirst方法即可,源码如下

public void addFirst(E e) {
linkFirst(e);
}

头部插入动画效果

2.2. 在中间位置插入

public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
} 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;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
  • node(index) 根据索引返回目标节点,内部会判断从头或尾遍历更快;

  • 连接新节点至前后节点。

在中间插入的动画效果

2.3. node(index)检索算法(重要)

链表数据结构无法像数组那样,可以直接根据索引下标和首地址来计算目标元素地址,只能从头部first节点或尾部tail节点一步一步的遍历到目标位置,从而获取到目标节点。

源码如下,(具体检索过程的动图可到修改元素章节感受)

Node<E> node(int index) {
// 如果索引在前半部分,从 head 开始向后遍历
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
// 否则从 tail 向前遍历
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}

算法解析

  1. 将链表逻辑分为两段:前半段 [0, size/2) 和后半段 [size/2, size)

  2. 若目标索引位于前半段,则从 first 开始,依次沿 next 找到第 index 个节点;

  3. 若在后半段,则从 last 开始,沿 prev 向前遍历直到到达;

  4. 这种折半遍历相当于对链表元素位置进行“裁剪”,最坏需要遍历 size/2 步,平均遍历 size/4 步,但仍为 O(n)

适用场景:在随机访问时,通过双向遍历能显著减少平均查找距离,提高链表访问效率。

3. 移除元素(删)

LinkedList 提供多种删除方式:

方法 说明
remove() 删除并返回头节点元素
remove(int index) 删除指定索引元素
remove(Object o) 删除首次匹配的元素
removeFirst() / pollFirst() 删除头部节点
removeLast() / pollLast() 删除尾部节点

3.1. unlink核心源码

unlink指的是断开节点连接,删除的本质是断开节点连接,使其前后节点重新连接,此时删除的节点在内存中就成了游离状态,后续会被GC清理回收。

E unlink(Node<E> x) {
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; if (next == null)
last = prev;
else
next.prev = prev; x.item = null;
x.next = null;
x.prev = null;
size--;
modCount++;
return element;
}
  • 调整前后节点指针,再断开目标节点;

  • GC 清理旧引用;

  • size--modCount++

3.2. 删除头部节点

删除头部节点并返回的源码:

// 删除并返回头部节点
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}

删除过程效果图:

3.3. 删除尾部节点

删除尾部节点并返回的源码:

public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}

删除过程效果图:

3.4. 删除指定索引节点

删除指定索引节点并返回的源码:

public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}

删除过程效果图:

4. 修改元素(改)

set(int index, E element):找到指定索引节点后直接覆盖

public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}

检索算法还是node(index),找到节点后直接修改节点内容的指向。

效果如图:将索引为2的-46改为88

如果在-46元素后面还多一个元素呢?效果如下:

5. 获取和检索元素(查)

5.1. 获取元素

public E get(int index) {
checkElementIndex(index);
return node(index).item;
}

通过 node(index) 遍历链表查找节点,复杂度 O(n)

5.2. 检索元素

  • contains(Object o) / indexOf / lastIndexOf:同 ArrayList,但比较的是链表节点的 item,需线性遍历,时间复杂度O(n)

6. LinkedList 的迭代器

LinkedList 实现了 双向迭代

  1. Iterator<E> iterator() 返回基于头到尾的迭代器;

  2. ListIterator<E> listIterator() 支持从任意位置开始双向遍历。

核心与 ArrayList 类似,使用 modCount 检测并发修改,next()/previous() 拿到节点后返回 item

7. 并发安全问题

LinkedList 同样线程不安全

多线程操作同一实例时需自行同步:

  • Collections.synchronizedList(new LinkedList<>())

  • 使用 CopyOnWriteArrayList 不适用于链表,可改用 ConcurrentLinkedDeque 作为替代;

8. 时间复杂度汇总

操作 时间复杂度 备注
头/尾插入 O(1) linkFirst / linkLast
中间插入 O(n) 查找节点 O(n) + 插入 O(1)
头/尾删除 O(1) unlinkFirst / unlinkLast
中间删除 O(n) 查找节点 O(n) + 删除 O(1)
修改元素 O(n) 查找节点 O(n)
获取元素 O(n) 查找节点 O(n)
检索/contains/indexOf O(n) 遍历比较
迭代 O(n) 每次 next O(1) 总 O(n)

9. 总结

LinkedList 适合频繁插入/删除的场景,尤其是在头尾操作;不适合随机访问,大规模 get/set 性能较差;在并发场景下需要显式同步或选用合适的并发链表实现;

根据场景选择:

  • 读多写少、随机访问ArrayList

  • 插入/删除频繁、双端操作LinkedList

Java集合源码--ArrayList的可视化操作过程

掌握设计模式的两个秘籍

查看往期设计模式文章的:设计模式

超实用的SpringAOP实战之日志记录

2023年下半年软考考试重磅消息

通过软考后却领取不到实体证书?

计算机算法设计与分析(第5版)

Java全栈学习路线、学习资源和面试题一条龙

软考证书=职称证书?

软考中级--软件设计师毫无保留的备考分享

觉得还不错的,三连支持:点赞、分享、推荐↓

Java集合--LinkedList源码可视化的更多相关文章

  1. Java集合---LinkedList源码解析

    一.源码解析1. LinkedList类定义2.LinkedList数据结构原理3.私有属性4.构造方法5.元素添加add()及原理6.删除数据remove()7.数据获取get()8.数据复制clo ...

  2. Java集合——LinkedList源码详解

    )LinkedList直接继承于AbstractSequentialList,同时实现了List接口,也实现了Deque接口. AbstractSequentialList为顺序访问的数据存储结构提供 ...

  3. Java集合-LinkedList源码分析

    目录 1.数据结构-链表 2.ArrayList结构特性 3.构造方法 4.成员变量 5.常用的成员方法 6.Node节点 7.序列化原理 8.迭代器 9.总结 1.数据结构-链表 链表(Linked ...

  4. 【java集合框架源码剖析系列】java源码剖析之LinkedList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 在实际项目中LinkedList也是使用频率非常高的一种集合,本博客将从源码角度带领大家学习关于LinkedList的知识. ...

  5. 【java集合框架源码剖析系列】java源码剖析之ArrayList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 本博客将从源码角度带领大家学习关于ArrayList的知识. 一ArrayList类的定义: public class Arr ...

  6. 自己根据java的LinkedList源码编写的一个简单的LinkedList实现

    自己实现了一个简单的LinkedList /** * Create by andy on 2018-07-03 11:44 * 根据 {@link java.util.LinkedList}源码 写了 ...

  7. 【java集合框架源码剖析系列】java源码剖析之TreeSet

    本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...

  8. 【java集合框架源码剖析系列】java源码剖析之HashSet

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...

  9. 【java集合框架源码剖析系列】java源码剖析之TreeMap

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于TreeMap的知识. 一TreeMap的定义: public class TreeMap&l ...

  10. 【java集合框架源码剖析系列】java源码剖析之HashMap

    前言:之所以打算写java集合框架源码剖析系列博客是因为自己反思了一下阿里内推一面的失败(估计没过,因为写此博客已距阿里巴巴一面一个星期),当时面试完之后感觉自己回答的挺好的,而且据面试官最后说的这几 ...

随机推荐

  1. Docker之一简介

    什么是Docker Docker是Google使用go语言进行开发的,对进程进行封装隔离,始于操作系统层面的虚拟化技术. 因为隔离的进程独立于宿主机和其它的隔离进程,因此成为容器 Docker在容器的 ...

  2. 公安部网防G01-网站安全卫士软件/linux防御

    公安部网防G01-该软件免费使用,安装在网站服务器上,利用操作系统内核加固和主机web流量过滤技术,有效检测并抵御网页篡改.SQL注入.漏洞攻击.暴力破解.木马控制.XSS跨站.CC拒绝服务.系统提权 ...

  3. BUUCTF--Dangeous RSA(小e)

    对于e很小,可以直接采取爆破的手段,直接上代码 点击查看代码 #python3 ## -*- coding: utf-8 -*-# import binascii from gmpy2 import ...

  4. 集合的通用遍历方法--java进阶day09

    1.集合的三种通用遍历方法 之前我们学习过集合的遍历方法,为什么这里还要再学呢? 这是因为,之前我们用的遍历方法使用了索引,但我们知道set接口的实现类的集合均无索引,所以我们要学习通用的遍历方法 2 ...

  5. java学习-6-核心类:字符串StringJoiner 和数组一起玩

    public class Main { public static void main(String[] args) { String[] names = {"Bob", &quo ...

  6. 【Guava】并发编程ListenableFuture&Service

    MoreExecutors directExecutor ExecutorService executor = Executors.newSingleThreadExecutor(); Settabl ...

  7. MySQL 中的事务隔离级别有哪些?

    MySQL 中的事务隔离级别有哪些? 在 MySQL 中,事务隔离级别用于定义一个事务能看到其他事务未提交的数据的程度.MySQL 支持以下四种事务隔离级别,每种级别对并发操作的支持程度和一致性要求不 ...

  8. php版10大设计模式,软件工程必须掌握的姿势

    作为一个半路出家的php萌新,在看公司老大们的代码时无时无刻不在感叹,老大就是老大,写的代码低耦合.易扩展,我怎么就想不出这写完美的实现方式,最近看了韩大佬的视频后才明白,原来这些都是业界前辈们总结提 ...

  9. 查阅相关资料, 了解什么是scrum中的3355?

    在Scrum中,3355是一个用于描述其核心组成部分的模型,具体包括三个核心角色.三个工件.五个关键事件和五个价值观.下面是对Scrum中3355的详细解释: 三个核心角色 产品负责人(Product ...

  10. Fastjson命令执行漏洞复现2(fastjson <=1.2.47)

    一.搭建环境: 第一种:Docker一键拉取环境 htttps://github.com/vulhub/vulhub/tree/master/fastjson/1.2.47-rce 第二种:tomac ...