Java 中 LinkedList 的底层源码

        在 Java 的集合框架中,LinkedList是一个独特且常用的成员。它基于双向链表实现,与数组结构的集合类如ArrayList有着显著差异。深入探究LinkedList的底层源码,有助于我们更好地理解其工作原理和性能特点,以便在实际开发中做出更合适的选择。

        这里如何具体在idea里查看底层源码的教程在我ArrayList那篇文章有,基本大差不差,具体步骤我就不再演示了,我直接把所有底层源码总结下来供大家参考。

咱们可以根据这个代码逻辑自己推一下,捋清楚了思路就好理解多了。

一、基本结构与成员变量

LinkedList的底层核心是一个双向链表,其内部通过节点来存储数据。以下是LinkedList源码中关键的成员变量定义:

// 头节点
transient Node<E> first;
// 尾节点
transient Node<E> last;
// 元素数量
transient int size = 0;
// 底层双向链表节点的内部类
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;}
}

firstlast分别指向双向链表的头节点和尾节点,通过它们可以方便地对链表进行遍历和操作。size变量用于记录链表中元素的个数。Node内部类则定义了链表节点的结构,每个节点不仅存储了元素值item,还持有指向前驱节点prev和后继节点next的引用,这使得双向链表能够灵活地在两个方向上进行遍历和元素的插入、删除等操作。

二、构造函数剖析

无参构造函数

public LinkedList() {
}

无参构造函数非常简洁,它只是创建了一个空的LinkedList对象。此时,firstlast都为nullsize为 0,链表中没有任何元素。

带集合参数的构造函数

public LinkedList(Collection<? extends E> c) {this();addAll(c);
}

该构造函数先调用无参构造函数创建一个空链表,然后通过addAll方法将传入集合中的所有元素添加到新创建的LinkedList中。这为我们在初始化LinkedList时提供了一种便捷的方式,可以直接将其他集合中的元素导入进来。

三、元素添加操作

在链表尾部添加元素

add(E e)方法用于在链表尾部添加一个元素,它是LinkedList中最常用的添加元素的方式之一。

public boolean add(E e) {linkLast(e);return true;
}
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;elsel.next = newNode;size++;modCount++;
}

add(E e)方法内部调用了linkLast方法。linkLast方法首先记录原链表的尾节点l,然后创建一个新的节点newNode,使其前驱指向原尾节点l,后继为null。接着更新last指向新节点,如果原尾节点为空,说明链表之前是空的,此时first也指向新节点;否则将原尾节点的后继指向新节点。最后,更新元素数量size和修改次数modCount。这个过程使得在链表尾部添加元素的操作非常高效,时间复杂度为 O (1)。

在指定位置插入元素

add(int index, E element)方法可以在链表的指定位置插入一个元素。

public void add(int index, E element) {checkPositionIndex(index);if (index == size)linkLast(element);elselinkBefore(element, node(index));
}

该方法首先调用checkPositionIndex方法检查索引是否越界(要求0 <= index <= size)。如果索引等于size,说明要在链表尾部插入元素,直接调用linkLast方法即可;否则,调用linkBefore方法在指定节点前插入元素。这里的node(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;}
}

node(index)方法通过判断索引与链表长度一半的大小关系,决定从链表头部还是尾部开始遍历查找指定索引位置的节点。如果索引小于链表长度的一半,从链表头部开始遍历;否则从链表尾部开始遍历。这种优化策略可以减少遍历的节点数量,提高查找效率。

四、元素删除操作

移除指定位置的元素

remove(int index)方法用于移除链表中指定位置的元素。

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

该方法先调用checkElementIndex方法检查索引是否越界(要求0 <= index < size),然后通过node(index)方法获取指定索引位置的节点,最后调用unlink方法移除该节点并返回其存储的元素。

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;x.prev = null;}if (next == null) {last = prev;} else {next.prev = prev;x.next = null;}x.item = null;size--;modCount++;return element;
}

unlink方法通过调整节点之间的引用关系,将指定节点从链表中移除。它先保存要移除节点的元素值、后继节点和前驱节点,然后根据前驱和后继节点的情况更新链表的头节点first或尾节点last,以及相关节点的前驱和后继引用。最后将被移除节点的元素值置为null,更新元素数量size和修改次数modCount,并返回被移除的元素。

移除指定元素的第一个匹配项

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);return true;}}} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item)) {unlink(x);return true;}}}return false;
}

该方法通过遍历链表,分别处理要移除的元素为null和不为null的情况。找到匹配的元素后,调用unlink方法将其移除并返回true;如果遍历完链表都没有找到匹配元素,则返回false

五、元素查找操作

查找指定元素首次出现的索引

indexOf(Object o)方法用于返回指定元素在链表中首次出现的索引,如果不存在则返回 -1。

public int indexOf(Object o) {int index = 0;if (o == null) {for (Node<E> x = first; x != null; x = x.next) {if (x.item == null)return index;index++;}} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item))return index;index++;}}return -1;
}

该方法从链表头部开始遍历,分别处理查找元素为null和不为null的情况,找到匹配元素时返回其索引,遍历完链表都未找到则返回 -1。

查找指定元素最后一次出现的索引

lastIndexOf(Object o)方法用于返回指定元素在链表中最后一次出现的索引,如果不存在则返回 -1。

public int lastIndexOf(Object o) {int index = size;if (o == null) {for (Node<E> x = last; x != null; x = x.prev) {index--;if (x.item == null)return index;}} else {for (Node<E> x = last; x != null; x = x.prev) {index--;if (o.equals(x.item))return index;}}return -1;
}

该方法从链表尾部开始遍历,分别处理查找元素为null和不为null的情况,找到匹配元素时返回其索引,遍历完链表都未找到则返回 -1。

通过对LinkedList底层源码的剖析,我们清楚地了解了它的结构和各种操作的实现原理。LinkedList在插入和删除元素时具有高效性,尤其是在链表中间位置进行操作时,不需要像ArrayList那样进行大量元素的移动。然而,由于其基于链表结构,随机访问元素的时间复杂度较高,需要遍历链表来查找指定位置的元素。因此,在实际开发中,我们应根据具体的需求和操作场景,合理选择使用LinkedList或其他集合类,以达到最佳的性能和功能实现。

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

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

相关文章

金蝶云星空k3cloud webapi报“java.lang.Class cannot be cast to java.lang.String”的错误

最近在对接金蝶云星空k3cloud webapi时&#xff0c;报一个莫名其妙的转换异常&#xff0c;具体如下&#xff1a; 同步部门异常! ERP接口登录异常&#xff1a;java.lang.Class cannot be cast to java.lang.String at com.jkwms.k3cloudSyn.service.basics.DeptK3CloudService.…

【Android】jni开发之导入opencv和libyuv来进行图像处理

做视频图像处理时需要对其进行水印的添加&#xff0c;放在应用层调用工具性能方面不太满意&#xff0c;于是当下采用opencvlibyuv方法进行处理。 对于Android的jni开发不是很懂&#xff0c;我的需求是导入opencv方便在cpp中调用&#xff0c;但目前找到的教程都是把opencv作为模…

【MySQL】centos 7 忘记数据库密码

vim /etc/my.cnf文件&#xff1b; 在[mysqld]后添加skip-grant-tables&#xff08;登录时跳过权限检查&#xff09; 重启MySQL服务&#xff1a;sudo systemctl restart mysqld 登录mysql&#xff0c;输入mysql –uroot –p&#xff1b;直接回车&#xff08;Enter&#xff09; 输…

国产编辑器EverEdit - 自定义标记使用详解

1 自定义标记使用详解 1.1 应用场景 当阅读日志等文件&#xff0c;用于调试或者检查问题时&#xff0c;往往日志中会有很多关键性的单词&#xff0c;比如&#xff1a;ERROR, FATAL等&#xff0c;但由于文本模式对这些关键词并没有突出显示&#xff0c;造成检查问题时&#xff…

Golang 并发机制-6:掌握优雅的错误处理艺术

并发编程可能是提高软件系统效率和响应能力的一种强有力的技术。它允许多个工作负载同时运行&#xff0c;充分利用现代多核cpu。然而&#xff0c;巨大的能力带来巨大的责任&#xff0c;良好的错误管理是并发编程的主要任务之一。 并发代码的复杂性 并发编程增加了顺序程序所不…

JVM 四虚拟机栈

虚拟机栈出现的背景 由于跨平台性的设计&#xff0c;Java的指令都是根据栈来设计的。不同平台CPU架构不同&#xff0c;所以不能设计为基于寄存器的。优点是跨平台&#xff0c;指令集小&#xff0c;编译器容易实现&#xff0c;缺点是性能下降&#xff0c;实现同样的功能需要更多…

鼠标拖尾特效

文章目录 鼠标拖尾特效一、引言二、实现原理1、监听鼠标移动事件2、生成拖尾元素3、控制元素生命周期 三、代码实现四、使用示例五、总结 鼠标拖尾特效 一、引言 鼠标拖尾特效是一种非常酷炫的前端交互效果&#xff0c;能够为网页增添独特的视觉体验。它通常通过JavaScript和C…

6-图像金字塔与轮廓检测

文章目录 6.图像金字塔与轮廓检测(1)图像金字塔定义(2)金字塔制作方法(3)轮廓检测方法(4)轮廓特征与近似(5)模板匹配方法6.图像金字塔与轮廓检测 (1)图像金字塔定义 高斯金字塔拉普拉斯金字塔 高斯金字塔:向下采样方法(缩小) 高斯金字塔:向上采样方法(放大)…

RNN/LSTM/GRU 学习笔记

文章目录 RNN/LSTM/GRU一、RNN1、为何引入RNN&#xff1f;2、RNN的基本结构3、各种形式的RNN及其应用4、RNN的缺陷5、如何应对RNN的缺陷&#xff1f;6、BPTT和BP的区别 二、LSTM1、LSTM 简介2、LSTM如何缓解梯度消失与梯度爆炸&#xff1f; 三、GRU四、参考文献 RNN/LSTM/GRU …

qt-Quick3D笔记之官方例程Runtimeloader Example运行笔记

qt-Quick3D笔记之官方例程Runtimeloader Example运行笔记 文章目录 qt-Quick3D笔记之官方例程Runtimeloader Example运行笔记1.例程运行效果2.例程缩略图3.项目文件列表4.main.qml5.main.cpp6.CMakeLists.txt 1.例程运行效果 运行该项目需要自己准备一个模型文件 2.例程缩略图…

以太坊入门【详解】

以太坊的组成部分 P2P网络&#xff1a;以太坊在以太坊网络上运行&#xff0c;该网络可在TCP端口30303上寻址&#xff0c;并运行一个协议。交易&#xff1a;以太坊交易时网络消息&#xff0c;其中包括发送者&#xff0c;接受者&#xff0c;值和数据的有效载荷以太坊虚拟机&…

实验十四 EL和JSTL

实验十四 EL和JSTL 一、实验目的 1、掌握EL表达式的使用 2、掌握JSTL的使用 二、实验过程 1、在数据库Book中建立表Tbook&#xff0c;包含图书ID&#xff0c;图书名称&#xff0c;图书价格。实现在bookQuery.jsp页面中模糊查询图书&#xff0c;如果图书的价格在50元以上&#…

安装和卸载RabbitMQ

我的飞书:https://rvg7rs2jk1g.feishu.cn/docx/SUWXdDb0UoCV86xP6b3c7qtMn6b 使用Ubuntu环境进行安装 一、安装Erlang 在安装RabbitMQ之前,我们需要先安装Erlang,RabbitMQ需要Erlang的语言支持 #安装Erlang sudo apt-get install erlang 在安装的过程中,会弹出一段信息,此…

音视频多媒体编解码器基础-codec

如果要从事编解码多媒体的工作&#xff0c;需要准备哪些更为基础的内容&#xff0c;这里帮你总结完。 因为数据类型不同所以编解码算法不同&#xff0c;分为图像、视频和音频三大类&#xff1b;因为流程不同&#xff0c;可以分为编码和解码两部分&#xff1b;因为编码器实现不…

ML基础-Jupyter notebook中的魔法命令

在 Jupyter Notebook 或 IPython 环境中&#xff0c;“魔法命令”&#xff08;Magic Commands&#xff09;是一些以百分号&#xff08;%&#xff09;或惊叹号&#xff08;!)开头的特殊命令&#xff0c;用于执行一些与代码运行环境相关的操作&#xff0c;而不仅仅是执行普通的 P…

【Unity2D 2022:UI】创建滚动视图

一、创建Scroll View游戏对象 在Canvas画布下新建Scroll View游戏对象 二、为Content游戏对象添加Grid Layout Group&#xff08;网格布局组&#xff09;组件 选中Content游戏物体&#xff0c;点击Add Competent添加组件&#xff0c;搜索Grid Layout Group组件 三、调整Grid La…

9-收纳的知识

[ComponentOf(typeof(xxx))]组件描述&#xff0c;表示是哪个实体的组件 [EntitySystemOf(typeof(xxx))] 系统描述 [Event(SceneType.Demo)] 定义事件&#xff0c;在指定场景的指定事件发生后触发 [ChildOf(typeof(ComputersComponent))] 标明是谁的子实体 [ResponseType(na…

数据库系统概念第六版记录 一

1.关系型数据库 关系型数据库&#xff08;Relational Database&#xff0c;简称 RDB&#xff09;是基于关系模型的一种数据库&#xff0c;它通过表格的形式来组织和存储数据。每个表由若干行&#xff08;记录&#xff09;和列&#xff08;字段&#xff09;组成&#xff0c;数据…

Vue前端开发-pinia之Actions插件

Store中的Actions部分&#xff0c;用于定义操作属性的方法&#xff0c;类似于组件中的methods部分&#xff0c;它与Getters都可以操作State属性&#xff0c;但在定义方法时&#xff0c;Getters是对State属性进行加工处理&#xff0c;再返回使用&#xff0c;属于内部计算;Action…

生成式AI安全最佳实践 - 抵御OWASP Top 10攻击 (下)

今天小李哥将开启全新的技术分享系列&#xff0c;为大家介绍生成式AI的安全解决方案设计方法和最佳实践。近年来生成式 AI 安全市场正迅速发展。据IDC预测&#xff0c;到2025年全球 AI 安全解决方案市场规模将突破200亿美元&#xff0c;年复合增长率超过30%&#xff0c;而Gartn…