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

news2025/4/6 3:53:54

参考笔记: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.coloradmin.cn/o/2328933.html

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

相关文章

Python办公自动化(2)对wordpdf的操作

一、操作word文档 终端下载操作word文件的工具库&#xff1a; pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple python-docx 1.遍历文档中内容 paragraphs&#xff1a;段落属性&#xff0c;返回列表类型的段落地址&#xff0c;遍历每一个段落地址&#xff0c;通过…

pip安装第三方库,但PyCharm中却无法识别

点击菜单栏File&#xff0c;选择Settings 系统默认的是PyCharm安装目录下的python.exe 解释器&#xff0c;不要用。 选择你的PYTHON的安装目录下的python.exe 解释器。如果不存在的话&#xff0c;增加进去 如果文件》设置打不开&#xff0c;需移除法化包。 打开 pycharm 安装目…

Linux C++编译及g++使用操作

编译的步骤 编译选项参数 编译生成库文件 静态库 动态库 运行可执行文件 静态库由于已经包含了链接的文件所以可以直接执行&#xff1b;动态库方式由于是运行时链接&#xff0c;所以需要指定链接的路径&#xff1b;

【Android】界面布局-线性布局LinearLayout-例子

线性布局&#xff08;LinearLayout&#xff09;是一种重要的界面布局中&#xff0c;也是经常使用到的一种界面布局 • 在线性布局中&#xff0c;所有的子元素都按照垂直或水平的顺序在界面上排列 ➢如果垂直排列&#xff0c;则每行仅包含一个界面元素 ➢如果水平排列&…

windows技术基础知识

NT架构 NT 就是new techonology 的英文单词缩写&#xff0c;是微软1993年推出操作系统的重大升级&#xff0c;如内存管理&#xff0c;安全机制&#xff0c;多任务&#xff0c;多线程支持。在此之前操作系统都是基于MS-DOS上面的图形化界面&#xff0c;只有有限的内存管理和多任…

在 Windows 环境下使用 VSCode 和 TinyGo 开发 ESP8266(NodeMcu) or STM32

支持的型号 https://tinygo.org/docs/reference/microcontrollers/ 1. 安装Go 2. 安装TinyGo&#xff0c;并添加环境变量 https://github.com/tinygo-org/tinygo/releases 3. VSCode配置&#xff0c;安装插件&#xff0c;选择设备 package mainimport ("machine"&q…

计算机视觉算法实战——基于YOLOv8的汽车试验场积水路段识别系统

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​ ​​​​​​​​​ ​​ 引言&#xff1a;汽车试验场智能化管理的迫切需求 在现代汽车研发流程中&#xff0c;试验场作为验证车辆性…

One API:LLM API 管理 分发系统,github 24.2K Star!

随着人工智能领域的不断发展&#xff0c;国内外各大厂商纷纷推出了自己的 AI 大模型。面对 DeepSeek、OpenAI、Claude、腾讯元宝等众多平台的 API 接口差异&#xff0c;开发者常常需要花费大量时间调整代码、处理密钥管理与流量调控。One API 正是在这种背景下诞生&#xff0c;…

Android Settings 有线网设置界面优化

Android Settings 有线网设置界面优化 文章目录 Android Settings 有线网设置界面优化一、前言二、简单修改1、修改的EthernetSettings代码&#xff1a;2、有线网ip获取代码&#xff1a;3、AndroidManifest.xml定义有线网的Activity4、修改后界面&#xff1a; 三、其他1、有线网…

正则入门到精通

​ 一、正则表达式入门​ 正则表达式本质上是一串字符序列&#xff0c;用于定义一个文本模式。通过这个模式&#xff0c;我们可以指定要匹配的文本特征。例如&#xff0c;如果你想匹配一个以 “abc” 开头的字符串&#xff0c;正则表达式可以写作 “^abc”&#xff0c;其中 …

微信小程序基于Canvas实现头像图片裁剪(上)

序言 嘿&#xff0c;打工人混迹职场这么久&#xff0c;图片处理肯定都没少碰。不过咱说实话&#xff0c;大部分时候都是直接 “抄近道”&#xff0c;用现成的三方组件&#x1f60f;。就像我&#xff0c;主打一个会用工具&#xff0c;毕竟善用工具可是咱人类的 “超能力”&…

基于VMware的Cent OS Stream 8安装与配置及远程连接软件的介绍

1.VMware Workstation 简介&#xff1a; VMware Workstation&#xff08;中文名“威睿工作站”&#xff09;是一款功能强大的桌面虚拟计算机软件&#xff0c;提供用户可在单一的桌面上同时运行不同的操作系统&#xff0c;和进行开发、测试 、部署新的应用程序的最佳解决方案。…

Ubuntu环境基于Ollama部署DeepSeek+Open-Webui实现本地部署大模型-无脑部署

Ollama介绍 Ollama是一款简单好用的模型部署工具,不仅可以部署DeepSeek,市面上开源模型大部分都可以一键部署,这里以DeepSeek为例 官网 DeepSeek 版本硬件要求 安装Ollama 环境 sudo apt update sudo apt install curl sudo apt install lsof1.命令一键安装 在官网点击…

goto在Java中的用法

说明&#xff1a;goto 在一些编程语言&#xff08;如C语言&#xff09;中&#xff0c;是用来表示跳转的&#xff0c;即代码执行到此处跳转到对应位置继续执行。 举例 举个例子&#xff0c;如下&#xff0c;是一个双层嵌套循环。现在我需要代码在内层循环符合某条件时跳出双层…

Vue3+Vite+TypeScript+Element Plus开发-03.主页设计与router配置

系列文档目录 Vue3ViteTypeScript安装 Element Plus安装与配置 主页设计与router配置 静态菜单设计 Pinia引入 文章目录 目录 系列文档目录 文章目录 前言 一、主页设计 二、配置代替别名 三、配置router 四、运行效果 五、参考文献 前言 本文将重点介绍如何使用…

无限滚动(Infinite Scroll)页面谷歌不收录!必须改回分页吗?

近三年&#xff0c;全球超过58%的网站采用无限滚动设计&#xff08;数据来源&#xff1a;PageTraffic 2023&#xff09; 谷歌官方数据显示&#xff0c;动态加载内容的索引失败率高达73%&#xff08;Google Webmaster Report 2022&#xff09;&#xff0c;而采用纯无限滚动的页…

Git相关笔记1 - 本地文件上传远程仓库

Git相关笔记 目录 Git相关笔记Git上传相关文件第一步创建一个仓库&#xff1a;第二步本地创建空文件夹&#xff1a;第三步开始在gitbush上传文件&#xff1a;解决外网网络连接的问题&#xff1a;中文文件的编码问题&#xff1a;参考资料 Git上传相关文件 第一步创建一个仓库&a…

如何计算财富自由所需要的价格?

写在前面&#xff1a;​【财富自由计算器】已上线&#xff0c;快算算财富自由要多少​ 多少钱&#xff0c;才能实现你的财富梦想&#xff1f; 需要多少&#xff0c;才能实现财务安全、财务独立&#xff0c;甚至财务自由&#xff1f; 看到结尾&#xff0c;你会很清楚地看到&…

thinkphp每条一级栏目中可自定义添加多条二级栏目,每条二级栏目包含多个字段信息

小程序客户端需要展示团购详情这种结构的内容,后台会新增多条套餐,每条套餐可以新增多条菜品信息,每条菜品信息包含菜品名称,价格,份数等字段信息,类似于购物网的商品多规格属性,数据表中以json类型存储,手写了一个后台添加和编辑的demo 添加页面 编辑页面(json数据…

深入解析ARM与RISC-V架构的Bring-up核心流程

深入解析ARM与RISC-V架构的Bring-up核心流程 作者&#xff1a;嵌入式架构探索者 | 2023年10月 引言 在嵌入式开发中&#xff0c;处理器的Bring-up&#xff08;启动初始化&#xff09;是系统运行的第一道门槛。ARM和RISC-V作为两大主流架构&#xff0c;其Bring-up流程既有共性…