【Java集合】LinkedList源码深度分析

参考笔记:java LinkedList 源码分析(通俗易懂)_linkedlist源码分析-CSDN博客


目录

1.前言

2.LinkedList简介

3.LinkedList的底层实现

4.LinkedList 与 ArrayList 的对比

4.1 如何选择

4.2 对比图

5.LinkedList 源码Debug

5.1 add(E e)

        (1)跳入无参构造

        (2) 跳入add方法

        (3)跳入linkLast方法

        (4)增加第一个元素成功 

        (5)向链表中添加第二个元素

5.2 remove()

        (1)准备工作

        (2) 跳入remove()方法

        (3)跳入removeFirst()方法

        (4)跳入unlinkFirst()方法

        (5) 第一个结点被删除


1.前言

         本文是对单列集合 List 的实现类之一 LinkedList 的源码解析。对于单列集合 List 的三个最常用的实现类—— ArrayList, Vector, LinkedList ,我对 ArrayList、Vector 的源码解读在另外两篇文章,感兴趣的话可以看看:

【Java集合】ArrayList源码深度分析-CSDN博客https://blog.csdn.net/m0_55908255/article/details/146886585【Java集合】Vector源码深度分析-CSDN博客https://blog.csdn.net/m0_55908255/article/details/146949740        注意 : 本文对 LinkedList 源码的解读基于主流的 JDK 8.0 的版本

2.LinkedList简介

 LinkedList 是一种常见的线性表,每一个结点中都存放了下一个结点的地址LinkedList 类位于 java.lang.LinkedList 下,其类定义和继承关系图如下:

  链表可分为单向链表和双向链表

        单项链表的每个结点:包含两个值——当前结点的值、下一个结点的地址值(以指向后一个结点)

        双向链表的每个结点:包含三个值——前一个结点的地址值(以指向前一个结点)、当前结点的值、下一个结点的地址值(以指向后一个结点)

③  LinkedList 底层实现了双向链表和双端队列的特点

 同 ArrayList、Vector 类似,可以向 LinkedList 集合中添加任意元素,包括 null,并且元素允许重复

 同 ArrayList 一样,LinkedList 也没有实现线程同步,因此 LinkedList 线程不安全

3.LinkedList的底层实现

LinkedList 的底层维护了一个双向链表。 LinkedList 类中维护了 first、last 属性,见名知意,它们分别指向双向链表的首结点和尾结点。其在源码中的定义如下:

②  链表中的每个结点( Node 对象)中又维护了 prev,next,item 三个属性,item 存放当前节点的值。通过 prev 指向前一个结点,通过 next 指向后一个结点,从而实现双向链表。此处的 Node<E> 类型,实际上是 LinkedList 类中维护的一个静态内部类,如下图所示 : 

③ LinkedList 中元素的添加和删除,在底层不是通过数组来完成的,而是通过链表来完成。因此 LinkedList 相对来说增删的效率更高

补充:为什么链表的增删效率比数组高?

相信学过数据结构的同学都知道,这里我简单提一下。数组如果删除中间的某一个元素可能需要挪动大量的数据,增加亦是如此。而链表只需要修改所删除节点的前后节点的 next、prev 即可,比较方便

④  双向链表的示意图如下 : (注意箭头是指向结点整体)

这里我用 Java 来模拟一个简单的双向链表,现在创建三个《诛仙》人物对象——陆雪琪、张小凡、小环,并且让他们形成如下的双向链表关系: 

代码如下:

public class demo {public static void main(String[] args) {//演示 : 用java模拟一个简单的双向链表//创建诛仙人物对象Node luxueqi = new Node("陆雪琪");        //第一个结点Node zhangxiaofan = new Node("张小凡");   //第二个结点Node xiaohuan = new Node("小环");       //第三个结点//完成双向链表//从左往右相连接luxueqi.next = zhangxiaofan;zhangxiaofan.next = xiaohuan;//从右往左相连接xiaohuan.prev= zhangxiaofan;zhangxiaofan.prev= luxueqi;//令first指向第一个结点,令last指向最后一个结点Node first = luxueqi;Node last = xiaohuan;//遍历链表(头 ——> 尾)System.out.println("从头-->尾");while (true) {if (first == null) {break;}System.out.println(first);      //输出当前对象的信息first = first.next;             //更改指向}System.out.println("=====================================");//遍历链表(尾 ——> 头)System.out.println("从尾-->头");while (true) {if (last == null) {break;}System.out.println(last);       //输出当前对象的信息last = last.prev;                //更改指向}}
}//自定义Node结点
class Node {public Object item;     //存放当前结点的数据public Node prev;       //指向前一个结点public Node next;       //指向后一个结点public Node(Object name) {this.item = name;}public String toString() {return "Node 's name = " + item;}
}

运行结果:

4.LinkedList 与 ArrayList 的对比

4.1 如何选择

① 增删操作多,优先选择 LinkedList ;改查操作多,优先选择 ArrayList

② 实际开发中,往往对集合的改查操作比较多,因此 ArrayList 一般用的较多

③ 根据实际业务需求来选择

④ ArrayListLinkedList 线程均不安全,建议单线程情况下使用

4.2 对比图

5.LinkedList 源码Debug

5.1 add(E e)

用以下代码为演示,进行 Debug 操作:

import java.util.LinkedList;
public class demo {public static void main(String[] args) {LinkedList linkedList = new LinkedList();linkedList.add(11);linkedList.add(141);System.out.println(linkedList);}
}

        (1)跳入无参构造

        如下图所示:

        可以看到, LinkedList 类的无参构造其实什么也没有做,这里只会利用隐藏的 super() 调用父类的无参构造器。因为它底层使用链表实现的,所以不需要创建数组

        直接跳出无参构造,可以看到 LinkedList 对象已经初始化完毕, LinkedList 维护的 first、last 属性经过了 默认初始化 ---> 显式初始化 ---> 构造器初始化,最后仍为默认值 null,如下图所示 :

        此时 firstlast 均为 null。所以,链表此时的结构如下图所示:

        (2) 跳入add方法

         由于我们向链表中添加的元素为 int 类型,所以跳入 add 方法之前会进行自动装箱 int ---> Integer,如下图所示:

        自动装箱结束后,跳入 add 方法,如下图所示:

        形参列表的 "e" 表示我们当前要添加的元素,所以此时 e = 11add 方法中调用了 linkLast 方法,在 linkLast 方法里面完成了添加元素的操作

        (3)跳入linkLast方法

        跳入 linkLast 方法,如下图所示:

        一步一步来看

        首先,Node 是"结点"的意思,就是 Node<E> 类型

        其次,本文上面提到说——此时 first == null,last == null。所以,linkLast 方法内,第一步是定义了一个 Node 类型的常指针 \color{red}l,并令其指向为 last,所以此时 \color{red}l=last=null

        接着,又定义了一个 Node 类型的常量 newNode 见名知意,"newNode" 就是我们要添加的新结点。那么,为 newNode 初始化的这个带参构造 new Node<>(l,e,null) 是怎么执行的呢?这三个实参分别是干嘛的?

        我们追入这个带参构造看个究竟:

        可以看到,构造器的三个形参就是 Node 对象的三个属性。所以,对应此处的三个实参,\color{red}l 就是 prev,此时为 nulle 就是已经装箱好的 11null 就是 next 的值

        因此, newNode 引用此时指向的就是一个前后结点均为空,值为 11 的新结点

        执行完带参构造,就是 last = newNode,即又令 last 指向了添加的新结点,使得 last 始终指向链表中的最后一个结点,这是典型的链表尾插法


        继续向下执行,是一个 if-else 的复合条件语句。判断条件 "l == null" 显然满足,令 first 也指向了该新结点;之后,又令 size 自增(size表示当前链表中结点的个数),modCount 也自增 1(modCount表示链表的修改次数)

        (4)增加第一个元素成功 

        linkLast 执行完毕后,此时的链表结构如下图所示:

        接下来,我们可以逐层跳出直到演示类中验证一下上面的链表结构图是否正确。此时链表的状态,如下图所示:

        可以看到,firstlast 确实是指向了同一个结点,并且该结点中 prev = null,next = null,item = 11  

        (5)向链表中添加第二个元素

        向链表中添加第 2 个元素,前面几个步骤都一样,这里就不再赘述了,直接从 linkLast 方法开始说起,如下 :  

        还是一步一步来看

        首先,Node 类型的常指针 \color{red}l 指向了 last 所指向的结点(即我们前面添加的第一个结点,此时它也为链表中的最后一个结点)

        其次,第二个新结点 newNode 进行初始化工作。注意,第一个实参 \color{red}l 代表的是新结点的prev,而 \color{red}l 此时指向的是第一个结点,因此,这一步实现了——令新节点 newNodeprev 指向了第一个结点

        接着,执行完带参构造,就是 last = newNode,即又令 last 指向了添加的新结点 newNode ,使得 last 始终指向链表中的最后一个结点,这是典型的链表尾插法

        之后,就是 if-else 的判断语句了,此时 \color{red}l 指向的是链表中的第一个结点,不为空,所以执行 else 中的语句,l.next = newNode,令第一个结点的 next 指向了新结点 

        最后,就是更新 size 、modCount

        综上,第二次 linkLast 方法执行完毕后,链表结构如下图所示:

🆗,以上就是 LinkedList add(E e) 方法源码分析

5.2 remove()

LinkedList 类的 remove 有三个重载方法:

① remove( ):删除链表的第一个结点

② remove(int index):删除链表中第 index-1 个结点

 remove(Object o):删除链表中与 o 值匹配的第一个结点

        (1)准备工作

        用以下代码演示 remove() ,进行 Debug 操作:

import java.util.LinkedList;
public class demo {public static void main(String[] args) {LinkedList linkedList = new LinkedList();linkedList.add(11);linkedList.add(141);linkedList.add(5);System.out.println("添加三个元素后,当前链表 = " + linkedList);linkedList.remove();System.out.println("删除第一个元素后,当前链表 = " + linkedList);}
}

        运行结果: 

        如代码所示,先在链表中加入三个元素:11,141,5 。则在删除元素之前,双向链表的结构如下图所示:

        (2) 跳入remove()方法

         我们直接在 remove 方法的调用行设置断点跳过去,并跳入 remove 方法,如下图所示 : 

        (3)跳入removeFirst()方法

        removeFirst 方法的源码如下: 

         首先,它令一个 Node 类型的常指针 f 指向了链表的第一个结点然后判断首结点是否为空。由于我们一开始就在链表中添加了 3 个元素,所以此处 f 肯定不为空。因此, if 语句中的内容会跳过,执行 return unlinkFirst(f)

        (4)跳入unlinkFirst()方法

        跳入 unlinkFirst 方法,其源码如下:

        一步一步来看

        首先第一条语句不用太关注。取出欲删除结点的 item 值只是为了最后 return 返回,与删除过程本身无关

        其次,第二条语句 final Node<E> next = f.next,它令一个 Node 类型的常指针 next 指向了 "第一个结点的next属性所指向的值",即指向了第二个结点,如下图所示 : 

        接着执行 f.item = null,f.next = null,置空了第一个结点的 itemnext ,并且令first 指向了第二个结点,如下图所示 :  

        继续 由于 next 现在指向的是第二个结点,不为空,所以接下里要进入 else 语句中。else语句中,next.prev = null,即将第二个结点的 prev 置为 null ,如下图所示 :  

        最后就是 size - 1,即链表中的结点个数减 1 modCount + 1,链表修改次数加 1 return elment,返回删除结点其对应的 item 值 

        (5) 第一个结点被删除

        至此,链表中的第一个结点被删除。回顾一下整个流程

        ① 将第一个结点的 item、next 置为 null

        ② first 指向了第二个结点

        ③ 将第二个结点的 prev 值置为 null切断其与第一个结点的"联系"

        ④ 经过①②③第一个结点被置于链表之外,之后会被 gc垃圾回收器 删除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/900693.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

openssl源码分析之加密模式(modes)

openssl实现分组加密模式&#xff08;例如AES128-CBC的CBC部分&#xff09;的模块名字叫做modes&#xff0c;源代码位于 https://gitee.com/gh_mirrors/openssl/tree/master/crypto/modes 博主又打不开github了TT&#xff0c;只能找个gitee镜像 头文件是modes.h。 该模块目前…

Java 搭建 MC 1.18.2 Forge 开发环境

推荐使用 IDEA 插件 Minecraft Development 进行创建项目 创建完成后即可进行 MOD 开发。 但是关于 1.18.2 的开发教程太少&#xff0c;因此自己研究了一套写法&#xff0c;写法并非是最优的但是是探索开发MOD中的一次笔记和记录 GITHUB: https://github.com/zimoyin/zhenfa…

nginx如何实现负载均衡?

Nginx 是一款高性能的 Web 服务器和反向代理服务器&#xff0c;它可以通过配置实现负载均衡功能。以下是实现负载均衡的详细步骤和方法&#xff1a; 1. 基本概念 负载均衡是将客户端请求分发到多个后端服务器上&#xff0c;以提高系统的可用性和性能。Nginx 支持多种负载均衡策…

深度学习天崩开局

李沐大神的d2l包导入&#xff0c; 这玩意需要python311版本&#xff0c;我现在版本已经313了&#xff0c;作为一个天生要强的男人&#xff0c;我是坚决不向低版本低头的。 然后我就研究啊&#xff0c;各种翻资料啊&#xff0c;然后deepseek加豆包都翻烂了&#xff0c; 最终所…

docker部署jenkins并成功自动化部署微服务

一、环境版本清单&#xff1a; docker 26.1.4JDK 17.0.28Mysql 8.0.27Redis 6.0.5nacos 2.5.1maven 3.8.8jenkins 2.492.2 二、服务架构&#xff1a;有gateway&#xff0c;archives&#xff0c;system这三个服务 三、部署步骤 四、安装linux 五、在linux上安装redis&#…

MPDrive:利用基于标记的提示学习提高自动驾驶的空间理解能力

25年4月来自南方科技大学、百度、英国 KCL和琶洲实验室&#xff08;广东 AI 和数字经济实验室&#xff09;的论文“MPDrive: Improving Spatial Understanding with Marker-Based Prompt Learning for Autonomous Driving”。 自动驾驶视觉问答&#xff08;AD-VQA&#xff09;…

Halcon图像采集

Halcon是一款强大的机器视觉软件&#xff0c;结合C#可以开发出功能完善的视觉应用程序。 基本设置 确保已经安装了Halcon和Halcon的.NET库&#xff08;HalconDotNet&#xff09;。 1. 添加引用 在C#项目中&#xff0c;需要添加对HalconDotNet.dll的引用&#xff1a; 右键点…

Win10定时任务计划无法显示要执行的EXE任务程序界面,问题解决办法

用C#开发的一款WINFORM程序&#xff0c;在电脑测试一切顺利&#xff0c;运行结果正确。但用电脑的定时任务执行时&#xff0c;程序界面不显示&#xff0c;重启电脑、各种试都不行&#xff0c;最终问题解决。 解决办法&#xff1a; 要选“只在用户登陆时运行”&#xff0c;才能执…

Navicat和PLSQL在oracle 使用语句报ORA-00911: 无效字符

后面我发现可能是在复制SQL语句中有中文&#xff0c;但是环境变量未配置中文环境。 因为Oracle的语法解析器特别严格&#xff0c;就会报出以上的错误出来。 SQL语句错误&#xff0c;存在中文字符或者sql语句空格导致&#xff0c;去掉即可解决。 我重新写语句&#xff0c;发现…

[ctfshow web入门] web30

信息收集 题目将flag system php不区分大小写地过滤了 解题 前置知识 print_r&#xff1a;php中用于打印数组 scandir&#xff1a;php中用于获取指点目录下的所以文件目录名 getcwd&#xff1a;获取当前目录 目录获取 这里提供两种方法 print_r(scandir(getcwd())); pri…

linux下MMC_TEST的使用

一:打开如下配置,将相关文件编译到内核里: CONFIG_MMC_TEST CONFIG_MMC_DEBUG CONFIG_DEBUG_FS二:将mmc设备和mmc_test驱动进行绑定 2.1查看mmc设备编号 ls /sys/bus/mmc/drivers/mmcblk/mmc0:aaaa2.2将mmc设备与原先驱动进行解绑 echo mmc0:aaaa >

《深度解析LightGBM与MySQL数据集成:高效机器学习的新范式》

在机器学习工程实践中&#xff0c;数据与模型的高效交互一直是制约算法性能发挥的关键瓶颈。LightGBM作为梯度提升决策树框架的杰出代表&#xff0c;其与关系型数据库MySQL的深度集成能力&#xff0c;为数据科学家提供了从原始数据到预测结果的完整解决方案。这种集成不是简单的…

处理Excel的python库openpyxl、xlrd、xlwt、pandas有什么区别,搞懂它

openpyxl、xlrd、xlwt、pandas 都能处理 Excel 表格&#xff0c;但用途和适合的场景不同。今天做个总结&#xff1a; 库名功能支持格式读写支持样式备注openpyxl全面的.xlsx处理库.xlsx&#xff08;Excel2007&#xff09;✅✅✅首选xlrd读取.xls文件的老牌工具.xls&#xff08…

EasyExcel-一款好用的excel生成工具

EasyExcel是一款处理excel的工具类&#xff0c;主要特点如下&#xff08;官方&#xff09;&#xff1a; 特点 高性能读写&#xff1a;FastExcel 专注于性能优化&#xff0c;能够高效处理大规模的 Excel 数据。相比一些传统的 Excel 处理库&#xff0c;它能显著降低内存占用。…

视频分析设备平台EasyCVR携手高空抛物AI智能分析技术,打造住宅小区头顶安全智能防线

一、背景介绍 随着城市化进程的高速推进&#xff0c;城市天际线不断被刷新&#xff0c;高楼大厦密密麻麻。然而&#xff0c;高空抛物问题也逐渐显现&#xff0c;这一行为不仅严重影响城市文明的形象&#xff0c;更带来很多安全隐患&#xff0c;威胁居民的生命财产安全&#xf…

Spring MVC 操作会话属性详解(@SessionAttributes 与 @SessionAttribute)

Spring MVC 操作会话属性详解&#xff08;SessionAttributes 与 SessionAttribute&#xff09; 1. 核心注解对比 注解作用范围功能SessionAttributes类级别声明控制器中需要持久化的模型属性&#xff08;存入 HttpSession&#xff09;SessionAttribute方法参数/返回值显式绑定…

Python字典实战: 三大管理系统开发指南(班级+会议+购物车)(附源码)

目录 摘要 一、班级管理系统&#xff08;含成绩模块&#xff09; 1. 功能概述 2. 完整代码与解析 3. 代码解析与亮点 二、会议管理系统 1. 功能概述 2. 完整代码 3. 代码解析与亮点 三、购物车管理系统 1. 功能概述 2. 完整代码 3. 代码解析与亮点 四、总结与扩…

北京自在科技:让万物接入苹果Find My网络的″钥匙匠″

在AirTag掀起全球防丢热潮的今天&#xff0c;越来越多的第三方产品开始接入苹果Find My网络——从充电宝到电动车&#xff0c;从行李箱到保温杯&#xff0c;用户只需打开iPhone的「查找」App&#xff0c;就能实时定位这些物品。 北京自在科技有限责任公司早在苹果推出Find My开…

Vue进行前端开发流程

一、创建vue项目 创建vue项目&#xff1a;先进入要操作的目录下&#xff0c;注意本项目是用vue2开发的。 vue create vue项目名 二、项目开发 1.创建项目结构 2.开发功能模块 主入口App.vue <template><div class"boss-app"><Header /><m…

网络带宽测速工具选择指南iperf3 nttcp tcpburn jperf使用详解

简介 本文主要介绍内网&#xff08;局域网&#xff09;与外网&#xff08;互联网&#xff09;的网络带宽测速工具下载地址、选择指南、参数对比、基本使用。 测速工具快速选择指南 测速工具下载地址 iperf 官网下载链接&#xff1a;iperf.fr/iperf-download.php该链接提供了不…