LinkedList源码解析和设计思路

一、 继承体系

LinkedList类位于java.util包中,它实现了List接口和Deque接口,LinkedList可以被当做链表、双端队列使用,并且继承自AbstractSequentialList类。在继承关系中,它的父类是AbstractSequentialList,而AbstractSequentialList又继承自AbstractListAbstractList继承自AbstractCollectionAbstractCollection实现了Collection接口。

二、设计目的

LinkedList的设计目的是为了提供一个基于链表的动态数组实现,它可以高效地进行插入、删除操作,并且能够按照索引快速访问元素。与ArrayList不同,LinkedList不需要预先分配固定大小的空间,因此适用于频繁插入、删除操作的场景。

三、框架总结结构

LinkedList采用双向链表的数据结构来存储元素,每个节点包含指向前一个节点和后一个节点的引用。它提供了add、remove、get等操作方法,以及支持队列和栈的操作方法。LinkedList还实现了ListIterator接口,可以进行双向迭代操作。

四、工作原理

在LinkedList中,每次添加或删除元素时,它会重新调整节点之间的引用关系,保证链表的正确性。在添加元素时,它会创建一个新的节点并将其插入到链表中,并更新前后节点的引用;在删除元素时,它会修改前后节点的引用,使得被删除节点脱离链表。由于LinkedList采用双向链表的结构,因此在插入和删除元素时,时间复杂度为O(1)。

五、如何创建LinkedList?

{transient int size = 0;/*** Pointer to first node.* Invariant: (first == null && last == null) ||*            (first.prev == null && first.item != null)*/transient Node<E> first; //指向第一个节点的指针,首节点的元素类型为 E/*** Pointer to last node.* Invariant: (first == null && last == null) ||*            (last.next == null && last.item != null)*/transient Node<E> last; //指向第一个节点的指针,首节点的元素类型为 E。/*** Constructs an empty list.*/public LinkedList() {}/*** Constructs a list containing the elements of the specified* collection, in the order they are returned by the collection's* iterator.** @param  c the collection whose elements are to be placed into this list* @throws NullPointerException if the specified collection is null*/public LinkedList(Collection<? extends E> c) {this();addAll(c);}
  • LinkedList类的构造函数。第一个构造函数是无参构造函数,用于构造一个空的LinkedList实例。
  • 第二个构造函数接受一个集合参数c,并将该集合中的元素按照迭代器返回的顺序添加到LinkedList中。首先调用了无参构造函数this()来初始化一个空的LinkedList,然后调用addAll方法将集合c中的所有元素添加到LinkedList中。
  • 这样设计的意义在于提供了创建LinkedList的灵活性,可以直接传入一个集合来初始化LinkedList。

六、add(E e)添加源码

public boolean add(E e) {linkLast(e);return true;}

linkLast(e)方法将元素e链接到链表的末尾,然后返回true表示添加元素成功。 linkLast(e) 方法会在链表末尾添加一个新节点,并更新链表的相关指针。

void linkLast(E e) {final Node<E> l = last;  // 将当前链表的最后一个节点赋值给变量l,用final修饰表示l是一个不可变的引用final Node<E> newNode = new Node<>(l, e, null);  // 创建一个新节点newNode,其中:// - 前驱节点指向l,即当前链表的最后一个节点// - 元素为e,即要添加到链表中的元素// - 后继节点暂时为null,因为这是新节点添加到最后一个位置last = newNode;  // 更新last指针,使其指向新节点newNode,表示新节点成为了链表中的最后一个节点if (l == null)first = newNode;  // 如果原链表为空(即l为null),将first指针指向新节点newNode,表示此时链表只包含一个节点,即newNodeelsel.next = newNode;  // 如果原链表不为空,则将当前链表最后一个节点的next指针指向新节点newNode,让新节点成为原链表中最后一个节点的后继节点size++;  // 增加链表的大小,表示成功在链表尾部添加了一个新节点modCount++;  // 修改计数器,用于记录对LinkedList结构进行修改的次数,以便在迭代过程中检测并发修改操作
}
  1. final Node<E> l = last;

    • 将当前链表的最后一个节点存储在变量l中,使用final关键字表示l是一个不可变的引用。
  2. final Node<E> newNode = new Node<>(l, e, null);

    • 创建一个新的节点newNode,包括:
      • 前驱节点指向l,即指向当前链表的最后一个节点
      • 元素为e,即要添加到链表中的元素
      • 后继节点暂时为null,因为这是要将新节点添加到链表末尾
  3. last = newNode;

    • 更新last指针,使其指向新创建的节点newNode,表示新节点成为了链表中的最后一个节点。
  4. if (l == null)

    • 检查原链表是否为空(即l为null)。
  5. first = newNode;

    • 如果原链表为空,则将first指针指向新节点newNode,表示此时链表只包含一个节点,即newNode。
  6. else

    • 如果原链表不为空,则执行以下操作:
  7. l.next = newNode;

    • 让当前链表最后一个节点的next指针指向新节点newNode,让新节点成为原链表中最后一个节点的后继节点。
  8. size++;

    • 增加链表的大小,表示成功在链表尾部添加了一个新节点。
  9. modCount++;

    • 修改计数器,用于记录对LinkedList结构进行修改的次数,以便在迭代过程中检测并发修改操作。

    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;}}
  1. E item;

    • 定义了一个泛型类型E的变量item,用于存储节点中的元素值。
  2. Node<E> next;

    • 定义了一个泛型类型为Node的变量next,表示指向链表中下一个节点的引用。
  3. Node<E> prev;

    • 定义了一个泛型类型为Node的变量prev,表示指向链表中上一个节点的引用。
  4. Node(Node<E> prev, E element, Node<E> next) {

    • 定义了Node类的构造函数,包括如下参数:
      • prev: 前驱节点的引用
      • element: 节点中的元素值
      • next: 后继节点的引用
  5. this.item = element;

    • 将传入的element赋值给节点的item属性,表示该节点存储了对应的元素值。
  6. this.next = next;

    • 将传入的next引用赋值给节点的next属性,表示该节点指向链表中的下一个节点。
  7. this.prev = prev;

    • 将传入的prev引用赋值给节点的prev属性,表示该节点指向链表中的上一个节点。

从头部追加(addFirst)

     LinkedList arrayList = new LinkedList ();arrayList.addFirst("头部");
    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;elsef.prev = newNode;size++;modCount++;} 
  1. private void linkFirst(E e) {

    • 定义了一个私有方法linkFirst,用于在链表的开头添加一个新节点,参数e表示要添加的元素。
  2. final Node<E> f = first;

    • 通过final关键字将当前链表的第一个节点(头节点)赋值给变量f。
  3. final Node<E> newNode = new Node<>(null, e, f);

    • 创建一个新的节点newNode,其中:
      • 前驱节点为null,因为新节点将成为头节点
      • 元素为e,即要添加到链表中的元素
      • 后继节点为f,即原来的头节点
  4. first = newNode;

    • 更新first指针,使其指向新创建的头节点newNode。
  5. if (f == null)

    • 检查原链表是否为空(即f为null)。
  6. last = newNode;

    • 如果原链表为空,则将last指针也指向新的头节点newNode。
  7. else

    • 如果原链表不为空,则执行以下操作:
  8. f.prev = newNode;

    • 将原来的头节点的prev指针指向新的头节点newNode,建立双向连接。
  9. size++;

    • 增加链表的大小,表示成功在链表开头添加了一个新的节点。
  10. modCount++;
    修改计数器,用于记录对LinkedList结构进行修改的次数,以便在迭代过程中检测并发修改操作。

七、remove(Object o)删除源码

public boolean remove(Object o) { // 定义一个方法,用于从数据结构中移除特定对象if (o == null) { // 如果传入的对象为空for (Node<E> x = first; x != null; x = x.next) { // 遍历链表直到最后一个节点if (x.item == null) { // 如果当前节点存储的对象为空unlink(x); // 调用unlink方法将该节点从链表中移除return true; // 返回true,表示移除成功}}} else { // 如果传入的对象不为空for (Node<E> x = first; x != null; x = x.next) { // 遍历链表直到最后一个节点if (o.equals(x.item)) { // 如果传入的对象等于当前节点存储的对象unlink(x); // 调用unlink方法将该节点从链表中移除return true; // 返回true,表示移除成功}}}return false; // 如果遍历完链表都没有找到匹配的对象,则返回false,表示移除失败
}

总结:如果传入的对象为空,则遍历链表直到找到存储的对象也为空的节点,然后将其从链表中移除。如果传入的对象不为空,则遍历链表直到找到与传入对象相等的节点,然后将其从链表中移除。如果未找到匹配的对象,则返回false表示移除失败。


/*** Unlinks non-null node x.*/
E unlink(Node<E> x) { // 定义了一个方法,用于移除非空节点xfinal E element = x.item; // 获取节点x存储的元素final Node<E> next = x.next; // 获取节点x的下一个节点final Node<E> prev = x.prev; // 获取节点x的前一个节点if (prev == null) { // 如果节点x的前一个节点为空first = next; // 将链表的头指针指向节点x的下一个节点} else {prev.next = next; // 将节点x的前一个节点的next指针指向节点x的下一个节点x.prev = null; // 将节点x的prev指针置为空}if (next == null) { // 如果节点x的下一个节点为空last = prev; // 将链表的尾指针指向节点x的前一个节点} else {next.prev = prev; // 将节点x的下一个节点的prev指针指向节点x的前一个节点x.next = null; // 将节点x的next指针置为空}x.item = null; // 将节点x的元素置为空size--; // 链表大小减一modCount++; // 修改次数加一return element; // 返回被移除节点x的元素
}

八、 get()查找

/*** Returns the (non-null) Node at the specified element index.*/
Node<E> node(int index) { // 定义一个方法,返回指定索引处的非空节点if (index < (size >> 1)) { // 如果索引小于链表大小的一半Node<E> x = first; // 从头部开始寻找节点for (int i = 0; i < index; i++) // 遍历直到找到对应索引的节点x = x.next; // 移动到下一个节点return x; // 返回找到的节点} else { // 如果索引大于等于链表大小的一半Node<E> x = last; // 从尾部开始寻找节点for (int i = size - 1; i > index; i--) // 逆序遍历直到找到对应索引的节点x = x.prev; // 移动到上一个节点return x; // 返回找到的节点}
}

根据索引值快速定位链表中的节点,通过前半部分和后半部分遍历的方式提高了效率。

LinkedList 总结用法和使用场景:

  • 用法:LinkedList 是一个双向链表实现的 List,支持在任意位置进行元素的插入和删除操作。可以作为队列(Queue)或双端队列(Deque)使用。
  • 使用场景:适合对列表进行频繁的插入和删除操作,因为插入和删除节点的开销较小。特别适合需要在中间插入元素的场景。由于每个节点在内存中不连续存储,可能会增加内存占用。

LinkedList 性能总结:

  • 查询性能:LinkedList 在查询时需要遍历链表,时间复杂度为 O(n),效率较低。
  • 插入/删除性能:在列表的开头或中间插入/删除元素时,LinkedList 的性能优于 ArrayList,因为只需要调整指针即可。
  • 迭代性能:LinkedList 在迭代访问元素时,效率较高,不会涉及数组复制等操作。

与 ArrayList 方法对比:

  • ArrayList:底层基于数组实现,随机访问快,但插入和删除元素开销较大,需要移动后续元素。适合读取操作多于修改操作的场景。
  • 对比
    • LinkedList 适合频繁插入和删除元素的场景,而 ArrayList 更适合于随机访问元素。
    • LinkedList 的插入和删除操作性能更好,但查询性能较差;ArrayList 的查询性能更好,但在插入和删除方面效率相对较低。

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

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

相关文章

go语言请求http接口示例 并解析json

本例请求了天气api接口 对接流程 注册一个账号, 对接免费实况天气接口阅读接口文档 http://tianqiapi.com/index/doc?versionday请求接口解析json 开发流程 创建一个 json.go 文件需要引入的包 import ("encoding/json""fmt""io/ioutil"&q…

设计模式--访问者模式(Visitor Pattern)

访问者模式&#xff08;Visitor Pattern&#xff09;是一种行为型设计模式&#xff0c;它可以让你在不改变类的情况下&#xff0c;增加作用于一组对象上的新操作。 访问者模式主要包含以下几个角色&#xff1a; Visitor&#xff08;访问者&#xff09;&#xff1a;这是一个接…

Spring炼气之路(炼气二层)

一、bean的配置 1.1 bean的基础配置 id&#xff1a; bean的id&#xff0c;使用容器可以通过id值获取对应的bean&#xff0c;在一个容器中id值唯一 class&#xff1a; bean的类型&#xff0c;即配置的bean的全路径类名 <bean id"bookDao" class "com.zhang…

软件测试 自动化测试selenium 基础篇

文章目录 1. 什么是自动化测试&#xff1f;1.1 自动化分类 2. 什么是 Selenium &#xff1f;3. 为什么使用 Selenium &#xff1f;4. Selenium 工作原理5. Selenium 环境搭建 1. 什么是自动化测试&#xff1f; 将人工要做的测试工作进行转换&#xff0c;让代码去执行测试工作 …

tailwindcss在vite esm模式下的配置修改

vite6将弃用cjs&#xff08;CommonJS &#xff09;采用ESM&#xff08;ESModule&#xff09;&#xff0c;所有的js文件将编译为ESM语法&#xff0c;参考https://cn.vitejs.dev/guide/troubleshooting 基于ESM方式&#xff0c;我们需要对导出导入方式和postcss插件加载方式进行…

macOS Ventura 13.6.5 (22G621) Boot ISO 原版可引导镜像下载

macOS Ventura 13.6.5 (22G621) Boot ISO 原版可引导镜像下载 3 月 8 日凌晨&#xff0c;macOS Sonoma 14.4 发布&#xff0c;同时带来了 macOS Ventru 13.6.5 和 macOS Monterey 12.7.4 安全更新。 macOS Ventura 13.6 及更新版本&#xff0c;如无特殊说明皆为安全更新&…

【开源鸿蒙】编译OpenHarmony轻量系统QEMU RISC-V版

文章目录 一、背景介绍二、准备OpenHarmony源代码三、准备hb命令3.1 安装hb命令3.2 检查hb命令 四、编译RISC-V架构的OpenHarmony轻量系统4.1 设置hb构建目标4.2 启动hb构建过程 五、问题解决5.1 hb set 报错问题解决 六、参考链接 开源鸿蒙坚果派&#xff0c;学习鸿蒙一起来&a…

Vue.js+SpringBoot开发农家乐订餐系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户2.2 管理员 三、系统展示四、核心代码4.1 查询菜品类型4.2 查询菜品4.3 加购菜品4.4 新增菜品收藏4.5 新增菜品留言 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的农家乐订餐系统&#xff0c…

ISIS接口MD5 算法认证实验简述

默认情况下&#xff0c;ISIS接口认证通过在ISIS协议数据单元&#xff08;PDU&#xff09;中添加认证字段&#xff0c;例如&#xff1a;MD5 算法&#xff0c;用于验证发送方的身份。 ISIS接口认证防止未经授权的设备加入到网络中&#xff0c;并确保邻居之间的通信是可信的。它可…

Blocks —— 《Objective-C高级编程 iOS与OS X多线程和内存管理》

目录 Blocks概要什么是BlocksOC转C方法关于几种变量的特点 Blocks模式Block语法Block类型 变量截获局部变量值__block说明符截获的局部变量 Blocks的实现Block的实质 Blocks概要 什么是Blocks Blocks是C语言的扩充功能&#xff0c;即带有局部变量的匿名函数。 顾名思义&#x…

八股文打卡day35——数据库(12)

面试题:讲一下MVCC机制&#xff1f; 我的回答&#xff1a; MVCC&#xff0c;是多版本并发控制。 什么意思呢&#xff1f; 数据库会记录每条数据记录的变更情况&#xff0c;也就是说&#xff0c;一条数据记录是有多个版本的。 例如&#xff0c;一条数据&#xff0c;是&#xf…

API安全集成最佳实践:有效应对安全挑战

API集成的重要性正愈发凸显。调查数据显示&#xff0c;83%的受访者表示API集成在其业务战略中起着关键作用&#xff0c;约40%的受访者表示企业数字化转型的深入发展是推动API集成的关键推动力。对于现代企业而言&#xff0c;API集成的重要性主要体现在以下方面&#xff1a; 提…

数据结构与算法Bonus-KNN问题的代码求解过程

一、问题提出 &#xff08;一&#xff09;要求 1.随机生成>10万个三维点的点云&#xff0c;并以适当方式存储 2.自行实现一个KNN算法&#xff0c;对任意Query点&#xff0c;返回最邻近的K个点 3.不允许使用第三方库(e.g.flann&#xff0c;PCL,opencv)! 4.语言任选(推荐…

ChatGPT编程实现简易聊天工具

ChatGPT编程实现简易聊天工具 今天借助[[小蜜蜂]][https://zglg.work]网站的ChatGPT练习socket编程&#xff0c;实现一个简易聊天工具软件。 环境&#xff1a;Pycharm 2021 系统&#xff1a;Mac OS 向ChatGPT输入如下内容&#xff1a; ChatGPT收到后&#xff0c;根据返回结…

深度学习设计-基于机器学习的心血管疾病分析与预测

概要 在国富民强的今天&#xff0c;医疗卫生事业快速发展&#xff0c;平均人口寿命也逐年上升&#xff0c;随之而来的是人口老龄化问题&#xff0c;而心 血管疾病是近年来发病率极高的老年性疾病。其发病率和死亡率均有所上升&#xff0c;已然成为当今威胁人类健康的重大疾 病之…

配置lvs(DR)

配置lvs(DR) 主机名主机IP地址lvs1lvs192.168.88.38web1nginx192.168.88.10web2nginx192.168.88.20 lvs1上操作 #安装ipvsadm [rootlvs1 ~]# yum -y install ipvsadm [rootlvs1 ~]# ipvsadm -A -t 192.168.88.100:80 -s rr [rootlvs1 ~]# ipvsadm -a -t 192.168.88.100:80 -…

【记录搭建elk 如何在linux共享文件】

『如何在linux共享文件 &#xff0c;搭建elk直接看第二部分』 新增用户a b c adduser a adduser b adduser c新增用户组 A groupadd developteam将用户a b c 加入 组 usermod -a -G developteam hadoop usermod -a -G developteam hbase usermod -a -G developteam hive设置um…

【学习笔记】云原生的关键技术初步

云原生&#xff08;Cloud Native&#xff09;作为云计算领域的一种新型技术体系&#xff0c;旨在提高应用程序的可靠性、性能和响应速度。它通过整合容器、微服务、DevOps等一系列关键技术&#xff0c;使得应用从设计开发到部署上线和运营维护的各个环节都基于云平台构建&#…

【GPT-SOVITS-06】特征工程-HuBert原理

说明&#xff1a;该系列文章从本人知乎账号迁入&#xff0c;主要原因是知乎图片附件过于模糊。 知乎专栏地址&#xff1a; 语音生成专栏 系列文章地址&#xff1a; 【GPT-SOVITS-01】源码梳理 【GPT-SOVITS-02】GPT模块解析 【GPT-SOVITS-03】SOVITS 模块-生成模型解析 【G…

microk8s使用本地私服registry的镜像http协议

开发环境为了能部署服务到microk8s&#xff0c;我们开启了一个本地私库&#xff0c;地址为&#xff1a;http://localhost:5000&#xff0c;那么如何在microk8s中能拉取本地私库中的镜像呢? 直接部署的话&#xff0c;microk8s会用https协议去拉取镜像&#xff0c;所以必须要配置…