Java LinkedList
LinkedList 一个双向链表。
本身是基于链表进行封装的列表, 所以具备了链表的特性: 变更简单, 容量是无限的, 不必像数组提前声明容量等。
同时 LinkedList 支持存储包括 null 在内的所有数据类型。
1 链表
了解 LinkedList 之前, 我们需要先了解一下双向链的特点
- 单链表, 双链表, 循环链表的定义, 可以看一下这个
- 链表内存是散乱的, 每一个元素存储本身内存地址的同时还存储下一个元素的地址
- 链表具备了增删快, 查找慢的特点
- LinkedList 是基于双向链表设计的, 所以具备了链表的特点
2 LinkedList 中每个节点的定义
private static class Node<E> {/** 当前节点的内容 */E item;/** 下一个节点 */Node<E> next;/** 上一个节点 */Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {// 当前节点的数据 = elementthis.item = element;// 当前节点的后置节点 = nextthis.next = next;// 当前节点的前置节点 = prevthis.prev = prev;}
}
Node 节点中除了指向下一个节点的 next, 还包含指向上一个节点的 prev, 所以 LinkedList 是一个双向的链表。
3 LinkedList 中的几个属性
public LinkedList<E> {transient int size = 0;transient Node<E> first;transient Node<E> last;
}
3.1 size
表示当前链表中的节点个数。
3.2 first
整个双向链表的头节点
3.3 last
整个双向链表的尾节点
重要的就这个几个属性了
4 LinkedList 的构造方法
public LinkedList<E> {// 构造函数 1: 无参构造函数public LinkedList() {}// 构造函数 2: 给定一个 Collection 的构造public LinkedList(Collection<? extends E> c) {// 省略}
}
4.1 无参的构造函数
public LinkedList() {
}
就是单纯的声明 LinkedList 的实例, 没有其他的逻辑了
4.2 给定一个 Collection 的构造
public LinkedList(Collection<? extends E> c) {// 调用自身无参的构造函数this();// 将集合 c 添加到当前的链表中addAll(c);
}/*** 将集合对象全部添加到当前链表中*/
public boolean addAll(Collection<? extends E> c) {// 调用 addAll 重写方法在 size 的位置添加数据return addAll(size, c);
}/*** 指定位置的批量插入*/
public boolean addAll(int index, Collection<? extends E> c) {// 判断 0 <= index <= size, 不满足抛出异常checkPositionIndex(index);// 集合转为数组Object[] a = c.toArray();int numNew = a.length;// 需要插入的集合没有数据, 直接结束if (numNew == 0)return false;// pred: 新增的元素的将要插入的位置的前一个节点 // succ: 新增的元素的将要插入的位置的节点// 如果新增的元素的节点位置为 a, 那么 pred 就是 a 的前一位的节点, succ 就是 a 节点Node<E> pred, succ;// 需要添加的元素刚好追加在已有的后面的if (index == size) {succ = null;pred = last;} else {// 获取指定位置的节点succ = node(index);pred = succ.prev;} for (Object o : a) {E e = (E) o;Node<E> newNode = new Node<>(pred, e, null);// 第一次创建时, 那么第一个节点就是 first节点if (pred == null)first = newNode;else// 将 pred 的下一个节点指向新创建的节点pred.next = newNode; // 将 pred 改为当前节点, 方便下一个元素操作pred = newNode; } // succ 是为空的, 表示直接在已有的数据后面追加元素的话, 所以将最后节点设置为新增元素的最后一个元素的节点if (succ == null) {last = pred;} else {// 原有数据后半部分拼接到现在链表的后半部分pred.next = succ;succ.prev = pred;} // 当前的长度size += numNew;// 修改次数加1 modCount++;return true;
}/*** 校验 index 的值满足: 0 <= index <= size*/
private void checkPositionIndex(int index) {if (!isPositionIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}/** 判断 index 是否大于等于 0 并且小于等于 size*/
private boolean isPositionIndex(int index) {return index >= 0 && index <= size;
}/*** 获取指定位置的节点*/
Node<E> node(int index) {// 此次做了一个小优化: 当要查找的位置小于现有数据的一半, 从前往后找, 大于的话, 从后面开始找// size >> 1 相当于 size / 2if (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 的数据是通过 first(节点) 和 last(节点) 和 size 三个共同维护的
- LinkedList 内部的数据通过泛型, 保持了自己的类型, 没有转为 Object。
5 LinkedList 几个常用的方法
5.1.1 添加数据
直接在链表末尾添加元素
// 直接添加一个元素
public boolean add(E e) {// 默认新增到链表的末尾linkLast(e);return true;
}/*** 将入参的元素添加到链表的末尾*/
void linkLast(E e) {// 获取链表的尾结点final Node<E> l = last;// 将数据封装为 Node 节点final Node<E> newNode = new Node<>(l, e, null);// 链表的尾结点等于新的节点last = newNode;// 如果链表一开始的尾结点为空, 表示链表一开始没有数据if (l == null)// 链表的头节点也等于当前的新节点first = newNode;else// 一开始的为节点有值, 将原来的尾结点的下一个节点设置为当前新的节点l.next = newNode; // 链表个数 + 1size++;// 修改次数 + 1modCount++
}
指定位置的插入
public void add(int index, E element) {// 判断 0 <= index <= size, 不满足抛出异常checkPositionIndex(index);// index == size 要插入的位置刚好在末尾if (index == size)linkLast(element);else // 插入到某个节点的前面linkBefore(element, node(index));
}/*** 将元素插入到某个节点的前面*/
void linkBefore(E e, Node<E> succ) {// 获取某个节点的前置节点final Node<E> pred = succ.prev;final Node<E> newNode = new Node<>(pred, e, succ);// 设置插入位置的节点的前置节点为需要插入的新节点succ.prev = newNode;// 插入位置的节点的前置节点为空, 表示没有头节点if (pred == null)first = newNode;else// 需要插入的位置的前置节点的下一个节点为新节点pred.next = newNode; // 个数加 1size++;// 修改次数 + 1modCount++;
}
在头部插入
public void addFirst(E e) {linkFirst(e);
}/*** 在链表的头节点前新增元素*/
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;else// 设置原本的头节点的前置节点为当前的节点f.prev = newNode;size++;modCount++;
}
还有前面看到的
- 插入一个 Collection 的 addAll(Collection c)
- 指定位置的插入一个 Collection 的 addAll(int index, Collection c)
5.1.2 删除数据
直接删除 (默认删除第一个元素)
public E remove() {return removeFirst();
}/*** 删除链表第一个元素*/
public E removeFirst() {final Node<E> f = first;// 头节点为空if (f == null)throw new NoSuchElementException();return unlinkFirst(f);
}/*** 删除指定的节点*/
private E unlinkFirst(Node<E> f) {// 获取删除节点的数据final E element = f.item;// 找到删除元素的下一个元素final Node<E> next = f.next;// 清空删除元素的信息f.item = null;f.next = null;// 将头节点重新设置为删除元素的下一个元素first = next;// 如果原本删除元素就是没有后续元素时, 将最后的元素设置为 nullif (next == null)last = null;else// 将新的第一个元素的前置节点设置为 nullnext.prev = null; // 元素个数 - 1 size--;// 修改次数 + 1modCount++;// 返回数据return element;
}
指定位置的删除
public E remove(int index) {// 检测删除的位置是否正确checkElementIndex(index);return unlink(node(index));
}private void checkElementIndex(int index) {// 如果不满足 0 <= index < size, 就抛出异常if (!isElementIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}private boolean isElementIndex(int index) {return index >= 0 && index < size;
}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;// 设置删除节点前置节点为 nullx.prev = null;}// 如果后置节点为空, 说明移除的为尾节点, 重新设置尾节点为删除节点的前置节点if (next == null) {last = prev;} else {// 将后置节点的前置节点设置为前置节点next.prev = prev;// 设置删除节点后置节点为 nullx.next = null;}// 将删除节点的数据设置为 nullx.item = null;size--;modCount++;return element;
}
指定删除元素
public boolean remove(Object o) {// 需要删除的节点为 nullif (o == null) {// 找到第一个为 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;}}}
}
除了上面几个还有
- 删除第一个 removeFirst()
- 删除最后一个 removeLast()
- 删除第一个符合的元素 removeFirstOccurrence(Object o)
- 删除最后一个符合的元素 removeLastOccurrence(Object o)
5.1.3 获取数据
获取指定位置的数据
public E get(int index) {// 确保 0 <= index < sizecheckElementIndex(index);// 定位到对应位置的节点的数据return node(index).item;
}
获取第一个的数据
public E getFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return f.item;
}
获取最一个的数据
public E getLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return l.item;
}
5.1.4 更新数据
更新指定位置节点的属性值
public E set(int index, E element) {// 确保 0 <= index < sizecheckElementIndex(index);// 获取指定位置的节点数据Node<E> x = node(index);// 获取指定节点是旧数据E oldVal = x.item;// 更新指定节点的数据x.item = element;// 返回旧数据return oldVal;
}
6 LinkedList 补充
1. LinkedList 实现了 Serializable 接口, 但是他的属性都是设置为 transient ?
LinkedList 重写了序列化方法 writeObject 和反序列化方法 readObject。 在序列化中, 重新通过遍历所有节点, 把所有节点数据写入到 ObjectOutputStream。
2. LinkedList 支持 fail-fast 机制 ?
通过继承关系可以指定 LinkedList 也是实现了 AbstractList, 具备了 modCount, 在修改中也会增加 modCount 的值, 所以 LinkedList 也支持 fail-fast 机制。
3. LinkedList不是一个线程安全的集合 ?
LinkedList 是线程不安全的, 如果需要保证线程的安全性, 可以考虑使用 Collections.synchronizedList(Lise l) 函数返回一个线程安全的 LinkedList 类。
4. 不要用 for 遍历 LinkedList ?
遍历的代码
List<String> list2 = new LinkedList<>();
list.add("1");
list.add("2");
list.add("3");for (int i = 0; i < list2.size(); i++) {String item = list2.get(i);System.out.println(item);
}
源码中涉及的代码:
public class LinkedList {public E get(int index) {checkElementIndex(index);return node(index).item;}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;}}
}
如上: 我们通过 for 遍历 LinkedList,
- 第一次 get(0), node(0), 就是 first 节点
- 第二次 get(1), node(1), 就是 first -> node1
- 第三次 get(2), node(2), 就是 first -> node1 -> node2
每次通过 get() 就是需要从第一个节点一直找到对应的位置, 才能找到需要的节点。
所以遍历 LinkedList 可以使用 foreach (foreach 循环的原理就是迭代器) 或者迭代器。
5. LinkedList 还有其他的作用吗
LinkedList 实现了 Deque 接口, 所以 LinkedList 可以作为双端队列, 同时 LinkedList 的双向链表的特点, 还可以作为 Stack 使用, 但是 LinkedList 的这 2 个功能,
如果没有什么特殊的要求的话, 都可以使用 ArrayDeque 替代, 后者的性能更好。
相关文章:
Java LinkedList
LinkedList 一个双向链表。 本身是基于链表进行封装的列表, 所以具备了链表的特性: 变更简单, 容量是无限的, 不必像数组提前声明容量等。 同时 LinkedList 支持存储包括 null 在内的所有数据类型。 1 链表 了解 LinkedList 之前, 我们需要先了解一下双向链的特点 单链表, 双…...
【单片机学习笔记】STC8H1K08参考手册学习笔记
STC8H1K08参考手册学习笔记 STC8H系列芯片STC8H1K08开发环境串口烧录 STC8H系列芯片 STC8H 系列单片机是不需要外部晶振和外部复位的单片机,是以超强抗干扰/超低价/高速/低功耗为目标的 8051 单片机,在相同的工作频率下,STC8H 系列单片机比传统的 8051约快12 倍速度…...
RevCol:可逆的柱状神经网络
文章目录 摘要1、简介2、方法2.1、Multi-LeVEl ReVERsible Unit2.2、可逆列架构2.2.1、MACRo设计2.2.2、MicRo 设计2.3、中间监督3、实验部分3.1、图像分类3.2、目标检测3.3、语义分割3.4、与SOTA基础模型的系统级比较3.5、更多分析实验3.5.1、可逆列架构的性能提升3.5.2、可逆…...
HCIA-RS基础-RIP路由协议
前言: RIP路由协议是一种常用的距离矢量路由协议,广泛应用于小规模网络中。本文将详细介绍RIP路由协议的两个版本:RIPv1和RIPv2,并介绍RIP的常用配置命令。通过学习本文,您将能够掌握RIP协议的基本原理、RIPv1和RIPv2的…...
虚拟化逻辑架构: LBR 网桥基础管理
目录 一、理论 1.Linux Bridge 二、实验 1.LBR 网桥管理 三、问题 1.Linux虚拟交换机如何增删 一、理论 1.Linux Bridge Linux Bridge(网桥)是用纯软件实现的虚拟交换机,有着和物理交换机相同的功能,例如二层交换&#…...
【Spring之AOP底层源码解析,持续更新中~~~】
文章目录 一、动态代理1.1、ProxyFactory1.2、Advice的分类1.3、Advisor的理解 二、创建代理对象的方式2.1、ProxyFactoryBean2.2、BeanNameAutoProxyCreator2.3、DefaultAdvisorAutoProxyCreator 三、Spring AOP的理解3.1、AOP中的概念3.2、Advice在Spring AOP中对应API3.3、T…...
C语言:有一篇文章,共三行文字,每行有80个字符。要求分别统计出单词个数、空格数。
分析: #include<stdio.h>:这是一个预处理指令,将stdio.h头文件包含到程序中,以便使用输入输出函数。 int main():这是程序的主函数,是程序执行的入口点。 char a[3][80];:定义了一个二维…...
【数据结构与算法篇】一文详解数据结构之二叉树
树的介绍及二叉树的C实现 树的概念相关术语树的表示 树的概念 树是一种非线性的数据结构,它是由n(n>0)个有限结点组成一 个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树, 也就是说它是根朝上,而叶朝…...
Windows主机信息收集命令
一.常用信息搜集 whoami # 查看当前用户 net user # 查看所有用户 query user # 查看当前在线用户 ipconfig /all # 查看当前主机的主机名/IP/DNS等信息 route print # 查看路由表信息 netstat -ano # 查看端口开放情况 arp -a # 查看arp解析情况 tasklist /svc # 查看进…...
「go module」一文总结 go mod 入门使用
文章目录 什么是 Go Modules为什么要使用 Modules怎么使用前置条件项目初始化如何安装/管理依赖?依赖安装 go get版本选择方式 替换版本 replace间接依赖 && go mod tidy远程代理 总结 什么是 Go Modules Module 是 Go 的依赖管理工具。 核心概念 Module…...
48. 旋转图像 --力扣 --JAVA
题目 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 解题思路 顺时针旋转90度 上下翻转 对角线翻转;两次两层循环…...
Java中的jvm——面试题+答案(Java虚拟机更深层次的概念和原理,包括字节码、代理、内存管理、并发等)——第17期
什么是即时编译(JIT Compilation)? 答案: 即时编译是一种在运行时将字节码转换为本地机器代码的技术,以提高程序的执行速度。JVM中的JIT编译器负责执行这个过程。 什么是Java字节码?为什么Java使用字节码…...
docker打包前端镜像
文章目录 一、构建镜像二、查看本地镜像三、启动容器四、查看启动的容器五、保存镜像六、读取镜像七、创建镜像八、最后 docker官网 一、构建镜像 -t是给镜像命名,.(点)是基于当前目录的Dockerfile来构建镜像 docker build -t image_web .二、查看本地镜像 docke…...
深入理解数据结构:链表
文章目录 🌰导语🌰链表的定义及基本结构🌰单链表🥕单链表特点 🌰双向链表🥕双链表特点 🌰循环链表🥕循环链表特点 🌰链表的操作🍆链表的插入🫘链头…...
7:kotlin 数组 (Arrays)
数组是一种数据结构,它保存固定数量的相同类型或其子类型的值。kotlin中最常见的数组类型是对象类型数组,数组由array类表示。 什么时候使用 当你在kotlin中有特殊的底层需求需要满足时,可以使用数组。例如,如果你有超出常规应用…...
mysql 变量和配置详解
MySQL 中还有一些特殊的全局变量,如 log_bin、tmpdir、version、datadir,在 MySQL 服务实例运行期间它们的值不能动态修改,也就是不能使用 SET 命令进行重新设置,这种变量称为静态变量。数据库管理员可以使用前面提到的修改源代码…...
算法基础之合并集合
合并集合 核心思想:并查集: 1.将两个集合合并2.询问两个元素是否在一个集合当中 基本原理:每个集合用一棵树表示 树根的编号就是整个集合的编号 每个节点存储其父节点,p[x]表示x的父节点 #include<iostream>using namespace std;const int N100010;int p[N];…...
在使用微信或者支付宝支付的时候,为什么微信支付或者支付宝支付的异步通知商户支付结果要进行验签?
在使用微信支付或支付宝支付等第三方支付平台时,异步通知是一种常见的机制,用于通知商户支付结果或交易状态的变化。验签(Signature Verification)是为了确保异步通知的安全性和完整性而进行的重要步骤。以下是为什么要进行验签的…...
带你用uniapp从零开发一个仿小米商场_5. 公共样式编写,
先前引入了公共样式,但公共样式文件里面还没有编写内容 在这里我将一一讲解公共样式内应该有什么样式,和为什么 首先给page添加高度和背景色,当然这个背景色可以在app.vue内添加 page{/* 设置page高,让每个页面的最小高度为整个视窗的高 */min-height: 100vh; /* 统一字体大小…...
2019年全国硕士研究生入学统一考试管理类专业学位联考英语(二)试题
文章目录 2019年考研英语二真题SectionⅠ Use of EnglishSection II Reading ComprehensionText 121——细节信息题22——细节信息题23——细节信息题24——细节信息题25——词义题 Text 226——细节信息题27——细节信息题28——细节信息题29——细节信息题30——态度题 Text …...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
