LinkedList底层结构和源码分析(JDK1.8)

news2025/3/15 10:04:54

参考视频:韩顺平Java集合

特点

  • LinkedList 底层实现了 双向链表双端队列 的特点。
  • 可以添加任意元素(元素可以重复),包括 null。
  • 线程不安全,没有实现同步。

LinkedList 底层结构

image.png|425

  • LinkedList 底层维护了一个双向链表
  • LinkedList 中维护了两个属性 firstlast 分别指向首节点和尾节点。
  • 每个节点(Node 对象)里面有维护了 prevnextitem 三个属性,其中通过 prev 指向前一个,通过 next 指向后一个节点。最终实现双向链表。
  • 所以 LinkedList 的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

模拟一个简单的双向链表

  • 定义一个Node类,表示一个双向链表的节点:
    public class Node {  
        public Object item;  
        public Node prev;//指向后一个节点  
        public Node next;//指向后一个节点  
      
        public Node(Object name){  
            this.item = name;  
        }  
      
        @Override  
        public String toString() {  
            return "Node name="+item;  
        }  
    }
    
  • 创建几个节点,并为其建立链表链接:
    //模拟一个简单的双向链表  
    @Test  
    public void test1(){  
        LinkedList list = new LinkedList();  
        Node node1 = new Node("Node1");  
        Node node2 = new Node("Node2");  
        Node node3 = new Node("Node3");  
      
        //链接三个节点,形成双向链表  
        //1->2->3  
        node1.next = node2;  
        node2.next = node3;  
        //3->2->1  
        node3.prev = node2;  
        node2.prev = node1;  
      
        //头节点  
        Node first = node1;  
        //尾节点  
        Node last = node3;
    }
    
  • 简单的应用:从头到尾遍历、从尾到头遍历:
    System.out.println("=======从头到尾进行遍历======");  
    //演示:从头到尾进行遍历  
    while(true){  
        if(first == null)break;  
        System.out.println(first);  
        first = first.next;//头节点移动至下一个  
    }  
      
    //演示:从尾到头进行遍历  
    System.out.println("=======从尾到头进行遍历======");  
    while (true){  
        if(last == null)break;  
        System.out.println(last);  
        last = last.prev;  
    }
    
  • 简单的应用:在 node1 和 node2 之间插入 node 1.5
    Node node15 = new Node("node1.5");  
    node15.prev = node1;  
    node15.next = node2;  
    node1.next = node15;  
    node2.prev = node15;
    
  • 遍历一下验证插入结果
    • (但是注意,这里所展示的遍历的例子是依靠 first 和 last 指针的移动来实现的,所以如果我们要遍历一遍插入新元素后的链表,则需要将 first 和 last 进行一个重新指向。)
    //头和尾要重新进行指向  
    first = node1;  
    last = node3;  
      
    System.out.println("=======从头到尾进行遍历======");  
    //演示:从头到尾进行遍历  
    while(true){  
        if(first == null)break;  
        System.out.println(first);  
        first = first.next;//头节点移动至下一个  
    }
    

LinkedList 增删改查——源码示例

  • 源码:
    • 构造器创建 linkedList 的细节和流程
    • 第一次添加元素(节点 1)
    • 第二次添加元素(节点 2)
    • 删除第一个节点
    • 删除下标为 2 的节点
    @Test  
    public void test2(){  
        LinkedList list = new LinkedList();  
        list.add(1);  
    	list.add(2);
        System.out.println("linkedList = "+list);  
    }
    

在 JDK 1.8 中,Node 的类是这样设计的:
image.png|425
与前面我们的简单实现相比,这里的 Node 中存放数据的 item 是使用泛型来约束的,这样在使用时可以定义其存储数据的类型。更加严谨一些。

构造器创建 LinkedList

  • 调用空参构造器创建了一个 size=0 的 linkedList。
  • 此时头和尾都为空,modCount 为修改次数
    image.png

第一次添加元素(节点 1)

  • 进入 add() 方法,将所添加的数据传递给 linkedLast()
    image.png|75
  • 注意此时链表为空,所以头节点和尾节点都为 null
    image.png
  • LinkedLast() 作用是将所添加的元素插在最后:
    image.png|450
  • var2=链表当前最后的元素。(因为此时链表为空,所以 var2=null)
  • 新建一个 Node,将其中的 item 值设置为 var1,并设置前节点(var2)和后节点(null)。这就是 var3。(此时 var2【null】➡var3【item=var1】➡null)
  • 让当前的尾节点指向 var3。(此时last➡var3)
  • 判断前一个节点( var2) 是否为空?
    • 如果 var2=null,也就代表原本是一个空链表,那么我们就需要将头结点指向新添加的节点 var3。(此时first➡var3⬅️last)
    • 如果 var2 不为空,就将 var2 的下一个节点指向 var3。
  • 链表长度+1 (此时size=1)
  • 修改次数+1 (此时monCount=1)
  • 至此,添加操作完成,回到添加的主逻辑:返回 true,添加完成。
    image.png
  • 可以看到数据已经放在了链表中,并且头节点和尾节点都指向该节点 (@861)
    image.png|425

第二次添加元素(节点 2)

  • 进入 add() 方法,将所添加的数据传递给 linkedLast() 中:
    image.png
  • LinkedLast() 作用是将所添加的元素插在最后:
    image.png|475
  • var2=链表当前最后的元素。(此时 var2=节点 1)
  • 新建一个 Node,将其中的 item 值设置为 var1,并设置前节点(var2)和后节点(null)。这就是 var3。(此时 var2【节点 1】➡var3【item=var1】➡null)
  • 让当前的尾节点指向 var3。(此时last➡var3)
  • 判断前一个节点( var2) 是否为空?
    • 如果 var2=null,也就代表原本是一个空链表,那么我们就需要将头结点指向新添加的节点 var3。
    • 如果 var2 不为空,就将 var2 的下一个节点指向 var3。(此时 var2【节点 1】➡var3)
  • 链表长度+1 (此时size=2)
  • 修改次数+1 (此时monCount=2)
  • 至此,添加操作完成,回到添加的主逻辑:返回 true,添加完成。
    image.png
  • 可以看到数据已经放在了链表中,并且头节点指向节点 1(@861),尾节点指向节点 2 (@867)
    image.png|400

删除第一个节点(无参,默认删除第一个)

  • 进入 remove() 方法中,可以发现其中调用了 removeFirst() 方法:
    image.png
  • removeFirst() 的作用就是删除链表的第一个元素。
  • var1=当前链表的头结点==(节点 1)==
  • 判断头结点是否为空,也就是判断链表是否为空
  • 为空则抛出异常;不为空则调用 unlinkFirst() 方法,将头节点传入:
    image.png
  • unlinkFirst() 的作用是删除链表的第一个节点:
  • var2=头节点中的数据 (本例中是 “1”)
  • var3=头节点的下一个节点 (本例中是节点 2)
  • 将头节点的数据清空
  • 将头节点的 next 置为 null;
  • 将头节点设置为下一个节点(var3)
  • 判断下一个节点是否为空(也就是判断该链表是否只有 var1 这一个节点)
    • 为空,则代表尾指针 last 也在 var1 这个要删除的节点上,所以需要将 last 也置为 null;
    • 不为空,则表示后续还有节点,把后节点的 prev 设置为 null。(本例)
  • 链表的长度-1 (size=9-1=8)
  • 修改次数+1 (modCount=9+1=10)
    image.png|500
  • 可以看到第一个节点已被删除。
    image.png|425

删除索引为 2 的节点

此时链表中的元素有【2,3,4,5,6,7,8,9】,我们要删除的是 “4”。

  • 进入 remove() 方法,先进入 checkElementIndex() 方法检查一下索引是否合法
    image.png|425
  • checkElementIndex() :检查索引是否合法 (目前链表 size=8,索引2 合法)
    image.pngimage.png|500
  • 回到 remove() 中,调用 unlink(node) 方法,其作用是删除一个节点 node。
    image.png|500
  • 但由于我们传入的是索引,而不是节点本身,所以需要调用 node (index) 方法来查找并返回节点。
  • 观察 node 方法:它的查找方式是检查要查找的节点索引在前半部分还是后半部分。(本例中我们查找索引 2,在前半部分,所以从头开始查找)
    • 如果是前半部分则从头开始查找;
    • 如果是后半部分,则从尾开始查找。
    • var2 是遍历用的节点
    • var3 遍历用的索引
      image.png|475
  • 最后将找到的节点返回给 unlink(node) 方法中。
    image.png
  • 找到的节点作为参数 var1 传入该方法。
  • var2=该节点的数据 (本例中是 “4”)
  • var3=该节点的后节点
  • var4=该节点的前节点
  • 判断前节点(var4)是否为 null
    • 如果 var4 为 null,则表示该节点为头节点,需要将 first 指针指向其下一个节点(var3);
    • 如果不为 null,说明其前面还有节点,就要将前节点(var4)的 next 指向其后节点(var3),并且将该节点的 prev 解除。(本例)
  • 判断后节点(var3)是否为 null
    • 如果 var3 为 null,则表示该节点为尾节点,需要将 last 指针指向其上一个节点(var3);
    • 如果不为 null,说明其后面还有节点,就要将后节点(var3)的 prev 指向其前节点(var4),并且将该节点的 next 解除。(本例)
  • 这样一来就将要删除的元素 var1 彻底与链表解除了关系,成为了一个孤立的节点。最后要做的就是将其中的数据(item)也进行清空
  • 链表长度-1 (size=8-1=7)
  • 修改次数+1 (modCount=10+1=11)
    image.png|425
  • 至此,删除下标为 2 的节点完成。

改:set (int, Object) 和查:get(i) 的源码都比较简单,可以自己分析一下。

LinkedList 遍历

由于 LinkedList 实现了 List 接口,而 List 接口可以使用三种方式遍历:迭代器、增强 for 循环、普通 for 循环。所以 LinkedList 也是支持的。

//增强for遍历  
System.out.println("======增强遍历");  
for (Object o :list) {  
    System.out.println(o);  
}  
  
//迭代器遍历  
System.out.println("======迭代器遍历");  
Iterator iterator = list.iterator();  
while(iterator.hasNext()){  
    Object next = iterator.next();  
    System.out.println(next);  
}  
  
//普通for遍历  
System.out.println("======普通for遍历");  
for (int i = 0; i < list.size(); i++) {  
    System.out.println(list.get(i));  
}

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

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

相关文章

数字内容体验的技术支柱是什么?

数据分析引擎构建基础 数字内容体验的技术底座始于对海量用户行为数据的深度解析。作为技术体系的根基&#xff0c;数据分析引擎通过实时采集、清洗与结构化处理&#xff0c;将分散的点击轨迹、停留时长及交互偏好转化为可操作的洞察。其核心能力体现在三方面&#xff1a;一是…

C# 使用Markdown2Pdf把md文件转换为pdf文件

NuGet安装Markdown2Pdf库&#xff0c;可以把格式简单markdown文件转换为pdf。但该库用了Puppeteer Sharp&#xff0c;因此会在运行过程中提示指定Chrome浏览器路径或自动下载Chrome浏览器。 代码如下&#xff1a; using Markdown2Pdf;var converter new Markdown2PdfConverte…

专家系统如何运用谓词逻辑进行更复杂的推理

前文&#xff0c;我们讲解了命题逻辑和谓词逻辑的基本概念、推理规则、应用以及一些简单的示例。具体内容可以先看我的文章&#xff1a;人工智能的数学基础之命题逻辑与谓词逻辑&#xff08;含示例&#xff09;-CSDN博客 那么形如专家系统这类复杂系统&#xff0c;是如何通过谓…

html css网页制作成品——糖果屋网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…

落雪音乐Pro 8.8.6 | 内置8条音源,无需手动导入,纯净无广告

洛雪音乐Pro版内置多组稳定音源接口&#xff0c;省去手动导入的繁琐操作&#xff0c;安装即可畅听海量音乐。延续原版无广告的纯净体验&#xff0c;支持歌单推荐与音源切换&#xff0c;满足个性化听歌需求。此版本仅支持在线播放&#xff0c;无法下载音乐&#xff0c;且与原版不…

什么是全栈?

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点下班 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 &#x1f4c3;文章前言 &#x1f537;文章均为学习工…

XML文件格式的简介及如何用Python3处理XML格式对象

诸神缄默不语-个人技术博文与视频目录 文章目录 1. XML格式简介2. 格式化XML文件的工具3. Python处理XML&#xff1a;xml库1. xml.etree.\(c\)ElementTree2. xml.dom.minidom 4. 本文撰写过程中参考的其他网络资料 1. XML格式简介 可扩展标记语言 (Extensible Markup Language…

通过qemu仿真树莓派系统调试IoT固件和程序

通过qemu仿真树莓派系统调试IoT固件和程序 本文将介绍如何使用 QEMU 模拟器在 x86 架构的主机上运行 Raspberry Pi OS&#xff08;树莓派操作系统&#xff09;。我们将从下载镜像、提取内核和设备树文件&#xff0c;到启动模拟环境&#xff0c;并进行一些常见的操作&#xff0…

Oracle底层原理解析

Oracle 解析 1、union \ union all \ Intersect \ Minus内部处理机制&#xff08;优化&#xff09; 当查询语句中的where子句中使用到or时&#xff0c;可以用union all来代替。因为使用or查询语句的时候&#xff0c;引起全表扫描&#xff0c;并走索引查询 特别&#xff1a;当…

深度解读DeepSeek部署使用安全(48页PPT)(文末有下载方式)

深度解读DeepSeek&#xff1a;部署、使用与安全 详细资料请看本解读文章的最后内容。 引言 DeepSeek作为一款先进的人工智能模型&#xff0c;其部署、使用与安全性是用户最为关注的三大核心问题。本文将从本地化部署、使用方法与技巧、以及安全性三个方面&#xff0c;对Deep…

【前端三剑客】万字总结JavaScript

一、初识JavaScript 1.1 JavaScript 的作用 表单动态校验&#xff08;密码强度检测&#xff09; &#xff08; JS 产生最初的目的 &#xff09;网页特效服务端开发(Node.js)桌面程序(Electron)App(Cordova)控制硬件-物联网(Ruff)游戏开发(cocos2d-js) 1.2 HTML/CSS/JS 的关系…

【哈希表与字符串的算法之路:思路与实现】—— LeetCode

文章目录 两数之和面试题01.02.判定是否为字符重排存在重复元素存在重复元素||字母异位词分组最长公共前缀和最长回文子串二进制求和字符串相乘 两数之和 这题的思路很简单&#xff0c;在读完题目之后&#xff0c;便可以想到暴力枚举&#xff0c;直接遍历整个数组两遍即可&…

Hello Mr. My Yesterday日文歌词附假名注音,祭奠逝去的青春

hello mr. my yesterday Hundred Percent Free Hello Mr. my yesterday云っておくれよ “夢叶うその瞬間にまた逢える”と 前方の幾多前途多難の未知 後方の道後悔も知った 経験と価値 夢なかば 一本の道結果だが ひとつだけ知りたいよ 神様がいるのなら “幸せの定義っ…

ubuntu ollama+dify实践

安装ollama 官网的指令太慢了&#xff0c;使用以下指令加速&#xff1a; export OLLAMA_MIRROR"https://ghproxy.cn/https://github.com/ollama/ollama/releases/latest/download" curl -fsSL https://ollama.com/install.sh | sed "s|https://ollama.com/dow…

S7-1200 G2移植旧版本S7-1200程序的具体方法示例

S7-1200 G2移植旧版本S7-1200程序的具体方法示例 前期概要: S7-1200 G2必须基于TIA博途V20,之前的程序可通过移植的方式在新硬件上使用。 该移植工具可自动将TIA Portal 项目从 S7-1200 移植到更新的S7-1200 G2。 注意: 该插件支持在同一TIA Portal项目实例内将软件和/或硬…

新办公室哪款空气净化器除甲醛效果好?高效除甲醛,提升效率

现代办公环境中&#xff0c;空气质量对员工的健康与工作效率产生着不可忽视的影响。尤其是新装修的办公室&#xff0c;往往因为空气中的甲醛浓度超标而导致一系列健康问题。因此&#xff0c;选择一款性能优越的除甲醛空气净化器就显得尤为重要。合适的空气净化器不仅可以有效过…

大视频背景暗黑风格的wordpress企业主题免费下载

整体风格是黑色的&#xff0c;首页首屏大视频背景&#xff0c;动态效果非常好。向下滚动时&#xff0c;滚动的特效也不错。 原文 https://www.bixugao.com/wp/26.html

C语言基础知识04

指针 指针概念 指针保存地址&#xff0c;地址是字节的编号 指针类型和保存的地址类型要一直 使用时注意&#xff0c;把地址转换为&变量的格式来看 int a[3]; a转为&a[0] 指针的大小 64bit 固定8字节&#xff0c; 32bit 固定4字节 指针…

VUE的脚手架搭建引入类库

VUE的小白脚手架搭建 真的好久好久自己没有发布自己博客了,对于一直在做后端开发的我 ,由于社会卷啊卷只好学习下怎么搭建前端,一起学习成长吧~哈哈哈(最终目的,能够懂并简易开发) 文章目录 VUE的小白脚手架搭建1.下载node.js2.安装vue脚手架3.创建一个项目4.代码规范约束配置(…

android lmkd.rc 介绍

service service lmkd /system/bin/lmkdclass coreuser lmkdgroup lmkd system readproccapabilities DAC_OVERRIDE KILL IPC_LOCK SYS_NICE SYS_RESOURCEcriticalsocket lmkd seqpacketpasscred 0660 system systemtask_profiles ServiceCapacityLow属于核心服务组&#xff0…