详解LinkedList中的底层实现

1.LinkedList的构造器

  • 无参构造器
/*** Constructs an empty list.*/
public LinkedList() {
}
  • 构造Collection: 只要是 Collection 下的实现类都可以被 LinkedList 构造

// ? extends E: 只要是E的子类及E类型的类都可以使用

public LinkedList(Collection<? extends E> c) {// 调用无参构造器this();// 把集合 c 中的所有元素都添加到 LinkedList 的对象中addAll(c);
}

2.添加元素

  • 头插: 把元素插入到链表的开头
public void addFirst(E e) {linkFirst(e);
}// 进行头插
private void linkFirst(E e) {// f 指向了头节点final Node<E> f = first;// 把 newNode 连接起来, newNode.next = f;final Node<E> newNode = new Node<>(null, e, f);// 让first 重新指向头节点first = newNode;// 判空if (f == null)last = newNode;elsef.prev = newNode; //连接起来后面的节点// 长度自增size++;modCount++;
}
  • 尾插: 把元素添加到链表的最后
public boolean add(E e) {linkLast(e);return true;
}// 真正进行尾插的地方
void linkLast(E e) {// l 指向 last, 即链表的最后一个位置final Node<E> l = last;// newNode 绑定了 prev, 即 newNode.prev = l;final Node<E> newNode = new Node<>(l, e, null);// 更新尾结点; last 为空, 则更新 newNode 为尾结点last = newNode;// l 为空, 则说明链表中一个节点都没有if (l == null)first = newNode; // 更新为头节点elsel.next = newNode; // 把 l.next 指向 newNode;size++; // 链表长度 +1modCount++;
}
  • 指定位置添加元素
public void add(int index, E element) {// 判断 index 是否合理checkPositionIndex(index);// index 为链表的长度if (index == size)linkLast(element); // 尾插else// node(index) 找到要插入的位置, 然后进行插入linkBefore(element, node(index));
}// succ 为要插入的位置, e 为要插入的值
// 尾插上面代码已经体现, 还要头插和任意位置插入未体现
void linkBefore(E e, Node<E> succ) {// assert succ != null;// pred 为插入位置的前一个final Node<E> pred = succ.prev;// 绑定 newNode.prev = pred, newNode.next = succ;final Node<E> newNode = new Node<>(pred, e, succ);// 前面已经知道了 newNode 的 prev 和 next, 还差 pred.next 和 succ.prev// 这个地方是绑定 succ.prevsucc.prev = newNode;// 说明要插入的节点是头节点if (pred == null)first = newNode; // 头插else// 任意位置, 绑定 pred.nextpred.next = newNode;size++; // 链表长度 +1modCount++;
}
  • 指定位置添加另一个集合中的所有元素
public boolean addAll(Collection<? extends E> c) {// 从 0 位置开始把集合 c 中的元素全部添加到 LinkedList 的对象中return addAll(size, c);
}// 具体实现细节
public boolean addAll(int index, Collection<? extends E> c) {// 检查下标是否正常checkPositionIndex(index);// 把集合 c 转化成 Object 数组Object[] a = c.toArray();// 获取数组长度int numNew = a.length;// 空数组, 直接返回, 无需构造if (numNew == 0)return false;// 定义前驱和后继节点Node<E> pred, succ;// index 的值和 size 相同, 则需要尾插(? 因为 size 代表当前 LinkedList 对象的元素个数)if (index == size) {succ = null; pred = last; // pred 指向最后一个元素} else {// succ 指向要插入位置的节点succ = node(index);// pred 指向要插入位置节点的前驱节点pred = succ.prev;}// 循环遍历数组 a// 要插入的所有值, 在 pred 的后面插入, 在 succ 的前面插入for (Object o : a) {@SuppressWarnings("unchecked") E e = (E) o; // 强制类型转化, 把 Object 转为特定类型// 进行插入, 让 newNode.prev 指向 pred, newNode.next = null;Node<E> newNode = new Node<>(pred, e, null);// 若 pred 为空, 则说明 succ 是头节点, 即说明前面是进入了 else 语句if (pred == null)first = newNode; // 更新头节点elsepred.next = newNode; // 让 pred 和 newNode 相互连接(串起来)pred = newNode; // 更新节点, 继续插入}// succ 为空, 则说明是尾插法, 即进入了 size == index 内部if (succ == null) {// last 指向最后一个元素last = pred;} else {// 连接 pred 和 succ 节点, 把链表串起来pred.next = succ;succ.prev = pred;}// 添加长度size += numNew;modCount++;return true;
}

3.删除元素

  • 头删: 把链表的最后一个元素删掉
public E removeFirst() {final Node<E> f = first;// 链表中一个节点也没有if (f == null)throw new NoSuchElementException();// unlinkFirst 方法中把头节点传过去return unlinkFirst(f);
}// 进行头删
private E unlinkFirst(Node<E> f) {// assert f == first && f != null;// 获取待删除节点的值final E element = f.item;// next 指向待删除节点的下一个节点final Node<E> next = f.next;// 把头节点的值置为 nullf.item = null;// 让 f(头节点)和后面的节点断开f.next = null; // help GC// 头节点重置first = next;// 说明 f 只有一个节点的情况if (next == null)last = null;elsenext.prev = null; // 把 next 的前驱也置为空, 彻底和 f 断开连接size--;modCount++;// 返回删除头节点的值return element;
}
  • 尾删: 把链表的最后一个元素删掉
public E removeLast() {final Node<E> l = last;// 一个节点都不存在if (l == null)throw new NoSuchElementException();// 把尾结点传到 unlinkLast 方法中return unlinkLast(l);
}// 进行尾删
private E unlinkLast(Node<E> l) {// assert l == last && l != null;// 获取待删除节点的值final E element = l.item;// 获取待删除节点的前一个节点final Node<E> prev = l.prev;// 把待删除节点的值域和 next 域均置为空l.item = null;l.prev = null; // help GC// 把尾结点重置到待删除节点的前一个节点last = prev;// 只有一个节点的情况, 则 prev 为空, 则需要把头节点也置为空if (prev == null)first = null;else// 删掉最后一个节点, 即和链表前面的链表断开连接prev.next = null;size--;modCount++;return element;
}
  • 删除指定节点: 那个节点值和传入要删除的值相同, 则删除该节点
// 删掉节点值与 o 相等的节点
public boolean remove(Object o) {// 判空if (o == null) {// 从头开始遍历节点for (Node<E> x = first; x != null; x = x.next) {// 判断那个的节点值为 null, 则删除这个节点if (x.item == null) {// 待删除的节点传到 unlink 中unlink(x);return true;}}} else { // 给的值不为 null// 遍历链表for (Node<E> x = first; x != null; x = x.next) {// 相等则删除if (o.equals(x.item)) {// 待删除的节点传到 unlink 中unlink(x);return true;}}}// 没有找到有相同的值return false;
}// 删除节点的逻辑
E unlink(Node<E> x) {// assert x != null;// 获取待删除节点的值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 节点// 把x节点的值置为空x.item = null;size--;modCount++;return element;
}

 

4.获取/修改元素

  • 获取index处的节点值
public E get(int index) {// 判断下标是否合法checkElementIndex(index);// 获取 index 处的值后, 返回return node(index).item;
}// 查找 index 处的节点值
Node<E> node(int index) {// assert isElementIndex(index);// 判断 index 是否在 size 的 1/2 内, 这个设计的目的是提高查找效率if (index < (size >> 1)) {// 定义查询节点Node<E> x = first;// 从头节点开始进行遍历, 一直到 index 处的节点位置for (int i = 0; i < index; i++)x = x.next;// 返回 index 处的节点的位置return x;} else {// 查找的节点在后半段Node<E> x = last;// 从尾结点向前遍历for (int i = size - 1; i > index; i--)x = x.prev;// 返回 index 处的节点return x;}
}
  • 修改index处节点的节点值
public E set(int index, E element) {// 判断下标是否合法checkElementIndex(index);// 获取 index 处的节点值Node<E> x = node(index);// 获取原来节点的节点值E oldVal = x.item;// 把节点的节点值进行更新x.item = element;// 返回原先的节点值return oldVal;
}

 

5.获取元素第一次/最后一次出现的位置

  • 获取元素第一次出现的位置
public int indexOf(Object o) {// 计数器, 用于获取目标节点处于那个下标int index = 0;if (o == null) {// 若是 o 为空, 也需要找到 null 在链表中的那个位置, 把这个位置返回for (Node<E> x = first; x != null; x = x.next) {if (x.item == null)return index;index++;}} else {// 不为空, 那么就判断那个节点值和当前的 o 相等, 相等则返回对应的下标for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item))return index;index++;}}// 没有找到的情况return -1;
}
  • 获取元素最后一次出现的位置
public int lastIndexOf(Object o) {// 计数器, 下标从链表的最后一个位置开始int index = size;if (o == null) {// 若是 o 为空, 也需要找到 null 在链表中的那个位置, 把这个位置返回// 从尾向头开始进行遍历for (Node<E> x = last; x != null; x = x.prev) {index--;if (x.item == null)return index;}} else {// 不为空, 那么就判断那个节点值和当前的 o 相等, 相等则返回对应的下标for (Node<E> x = last; x != null; x = x.prev) {index--;if (o.equals(x.item))return index;}}// 链表中不存在对应的值return -1;
}

 

6.链表长度

public int size() {return size;
}// size 是 LinkedList 中的一个计数器, 用于记录链表长度的
transient int size = 0;

 

7.清空链表

public void clear() {// Clearing all of the links between nodes is "unnecessary", but:// - helps a generational GC if the discarded nodes inhabit//   more than one generation// - is sure to free memory even if there is a reachable Iterator// 遍历链表, 从头开始一个一个释放所占空间for (Node<E> x = first; x != null; ) {// 记录 x 的下一个节点Node<E> next = x.next;// 把 x 节点释放x.item = null;x.next = null;x.prev = null;// 指向下一个节点x = next;}// 头节点和尾结点置为 nullfirst = last = null;// 长度置为 0size = 0;modCount++;
}

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

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

相关文章

Angular v19 (三):增量水合特性详解 - 什么是水合过程?有哪些应用场景?与 Qwik 相比谁更胜一筹?- 哪个技术好我就学哪个,这就是吸心大法吧

Angular在其最新版本 v19 中引入了增量水合&#xff08;Incremental Hydration&#xff09;这一特性。这一更新引发了开发者们广泛的讨论&#xff0c;特别是在优化首屏加载速度和改善用户体验方面。本文将详解水合过程的概念、增量水合的应用场景&#xff0c;以及它与类似框架如…

各类 AI API获取方法,GPT | Claude | Midjourney等

前言 在当今数字化转型的浪潮中&#xff0c;企业和开发者都面临着前所未有的技术挑战与机遇。随着ChatGPT等大语言模型的崛起&#xff0c;AI应用开发已从可选项变成了必选项。在AI应用开发中&#xff0c;成本控制是一个普遍的痛点。单是API调用费用就包含了多个维度&#xff1…

Linux:进程间通信之system V

一、共享内存 进程间通信的本质是让不同的进程看到同一份代码。 1.1 原理 第一步&#xff1a;申请公共内存 为了让不同的进程看到同一份资源&#xff0c;首先我们需要由操作系统为我们提供一个公共的内存块。 第二步&#xff1a;挂接到要通信进程的地址空间中 &#xff…

Python数组拆分(array_split())

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

微信小程序——文档下载功能分享(含代码)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

LabVIEW氢气纯化控制系统

基于LabVIEW的氢气纯化控制系统满足氢气纯化过程中对精确控制的需求&#xff0c;具备参数设置、过程监控、数据记录和报警功能&#xff0c;体现了LabVIEW在复杂工业控制系统中的应用效能。 项目背景 在众多行业中&#xff0c;尤其是石油化工和航天航空领域&#xff0c;氢气作为…

【linux】(23)对象存储服务-MinIo

MinIO 是一个高性能的对象存储服务&#xff0c;兼容 Amazon S3 API。 Docker安装MinIo 前提条件 确保您的系统已经安装了 Docker。如果还没有安装 Docker&#xff0c;可以参考 Docker 官方文档进行安装。 1. 拉取 MinIO Docker 镜像 首先&#xff0c;从 Docker Hub 拉取 Mi…

(超详细图文)PLSQL Developer 配置连接远程 Oracle 服务

1、下载配置文件 &#xff08;超详细图文详情&#xff09;Navicat 配置连接 Oracle-CSDN博客 将下载的文件解压到单独文件夹&#xff0c;如&#xff1a;D:\App\App_Java\Oracle\instantclient-basic-windows.x64-19.25.0.0.0dbru 2、配置 打开 PLSQL Developer&#xff0c;登…

【网络篇】HTTP知识

键入网址到网页显示&#xff0c;期间发生了什么&#xff1f; 浏览器第一步是解析URL&#xff0c;这样就得到了服务器名称和文件的路径名&#xff0c;然后根据这些信息生成http请求&#xff0c;通过DNS查询得到我们要请求的服务器地址&#xff0c;然后添加TCP头、IP头以及MAC头&…

C 语言学习的经典书籍有哪些?

学习C语言的理由 C语言是一种程席设计语言&#xff0c;它是由美国AT&T公司贝尔实验室的Dennis Ritchie于1972年发明的。C语言之所以流行&#xff0c;是因为它简单易用。学习C语言的几个理由如下&#xff1a; (1)C、C#和Java使用一种被称为面向对象程序设计(0bject-Orient…

webrtc ios h264 硬编解码

webrtc ios h264 硬编解码 一 ios 系统支持 从ios8开始&#xff0c;苹果公司开放了硬解码和硬编码API&#xff08;即 VideoToolbox.framework API&#xff09; 二 主要api 1 主要解码函数 VTDecompressionSessionCreate // 创建解码 session VTDecompressionSession…

RVO动态避障技术方案介绍

原文&#xff1a;RVO动态避障技术方案介绍 - 哔哩哔哩 我们在开发游戏的时候经常会遇到这样的问题&#xff0c;当我们寻路的时候&#xff0c;其它人也在寻路&#xff0c;如何避免不从其它人的位置穿过。这个叫做动态避障&#xff0c;目前主流的解决方案就是RVO。本节我们来介绍…

(免费送源码)计算机毕业设计原创定制:Java+ssm+JSP+Ajax SSM棕榈校园论坛的开发

摘要 随着计算机科学技术的高速发展,计算机成了人们日常生活的必需品&#xff0c;从而也带动了一系列与此相关产业&#xff0c;是人们的生活发生了翻天覆地的变化&#xff0c;而网络化的出现也在改变着人们传统的生活方式&#xff0c;包括工作&#xff0c;学习&#xff0c;社交…

对比学习与自监督任务

对比学习与自监督任务 笔者在之前上课时候被迫接受学习了NLP的许多相关的知识&#xff0c;BERT GPT等许多的NLP领域的大模型&#xff0c;都采用了半监督或者说是无监督的训练方法&#xff0c;最近在视觉的领域视觉自监督的模型受到了越来越多的关注。现在需要自己了解一下自监督…

SpringCloud2~~~

Nacos Nacos就是替代 注册中心【Eureka】 和 配置中心【Config】 支持AP和CP&#xff0c;可以切换 了解即可 下载和运行 下载版本&#xff08;找自己想要的版本&#xff09;&#xff1a;Tags alibaba/nacos GitHub 本地有良好的 Java8 Maven环境 解压安装包&#xff0c;直接…

Vue进阶之单组件开发与组件通信

书接上篇&#xff0c;我们了解了如何快速创建一个脚手架&#xff0c;现在我们来学习如何基于vite创建属于自己的脚手架。在创建一个新的组件时&#xff0c;要在新建文件夹中打开终端创建一个基本的脚手架&#xff0c;可在脚手架中原有的文件中修改或在相应路径重新创建&#xf…

VPS默认是通过密钥文件登陆机器,编译~让机器能直接通过root密码登陆。

SSH 登录机器 登陆机器 输入命令切换到root权限并修改密码&#xff1a; sudu su #切换root权限psddwd #修改密码 修改登陆方式 输入命令&#xff1a; vi /root/.ssh/authorized_keys 找到 “ssh-rsa”字样&#xff0c; 按键盘 ” i ” 进入编辑模式&#xf…

map用于leetcode

//第一种map方法 function groupAnagrams(strs) {let map new Map()for (let str of strs) {let key str ? : str.split().sort().join()if (!map.has(key)) {map.set(key, [])}map.get(key).push(str)} //此时map为Map(3) {aet > [ eat, tea, ate ],ant > [ tan,…

文件比较和文件流

文件比较和文件流 一、文本比较工具 diff1.基本用法1.1输出格式 2.常用选项 二、文件流1.文件的打开模式2.文件流的分类ifstreamofstreamfstrem区别 3.文件流的函数1. 构造函数2. is_open 用于判断文件是否打开3. open4. getline5. close6. get()7. read8. write9. put10. gcou…

《如何使用Unity的Avatar人偶以及启动重定向-实现2个或多个人物模型使用同一个动画片段》

8.5 使用Avatar和人物重定向 注意事项&#xff1a; 这个人偶以及重定向技术只能作用于人物模型&#xff01; 这个人偶以及重定向技术只能作用于人物模型&#xff01; 这个人偶以及重定向技术只能作用于人物模型&#xff01; 1. 基本原理 在Unity中&#xff0c;Avatar人偶和…