图解LinkedList底层原理

图解LinkedList底层原理


本篇将讲解Java中的一个集合LinkedList的底层实现原理,会查看并分析底层源码,结合图解的方式,理解其添加数据的过程

数据结构

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;}
}
  • 每个节点存储当前元素的值(item)及指向前后节点的引用(prevnext
  • LinkedList 是一个双向链表,支持从头部或尾部高效地插入和删除
First
Node 1
prev: null
item
next: Node2
Node 2
prev: Node1
item
next: Node3
Node 3
prev: Node2
item
next: Last
Last

成员变量

LinkedList 的核心成员变量如下:

transient int size = 0;      // 链表中元素的数量
transient Node<E> first;     // 链表的头节点
transient Node<E> last;      // 链表的尾节点
  • size:记录链表中元素的数量
  • first:指向链表的头节点
  • last:指向链表的尾节点

构造方法

LinkedList 提供了默认的无参构造方法:

public LinkedList() {}
  • LinkedList 初始化时,firstlast 都为 null,表示链表为空。

add 方法源码分析

当我们调用 add(E e) 方法时,例如:

LinkedList<String> list = new LinkedList<>();
list.add("Hello");

以下是 add 方法的源码:

public boolean add(E e) {linkLast(e);return true;
}
  • add 方法通过调用 linkLast(E e) 方法,将新元素添加到链表尾部

linkLast 方法解析

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;    // 更新旧尾节点的 next 引用size++;                  // 链表长度加 1modCount++;              // 修改计数器
}

当我add第一个元素e时,他的插入过程如下:

  1. 新节点创建

    final Node<E> l = last;  // 获取当前尾节点
    final Node<E> newNode = new Node<>(l, e, null)
    last = newNode;          // 更新尾节点
    

    通过 Node 构造方法,将新节点的 prev 指向当前尾节点,当前尾节点为null,所以prev指向null,next 指向 null,再将尾节点last指向新节点

    First
    Node 1
    prev: null
    item:e
    next:null
    Last
  2. 更新链表结构

    if (l == null)           // 如果链表为空first = newNode;     // 新节点即为头节点elsel.next = newNode;
    
    • 如果链表为空(l == null),更新头节点 first
    • 如果链表非空,将当前尾节点的 next 指向新节点

    此时的l是未更新之前的last,为null,所以更新头节点也指向Node 1

    First
    Node 1
    prev: null
    item:e
    next:null
    Last
  3. 更新尾节点和链表长度:新节点成为尾节点,size 自增


接着我们再add第二个元素e2,同样的步骤再次执行:

  1. 新节点创建

    final Node<E> l = last;  // 获取当前尾节点
    final Node<E> newNode = new Node<>(l, e, null)
    last = newNode;          // 更新尾节点
    

    在添加第一个元素时,尾节点更新为了Node 1,此时的把尾节点赋值给ll的值为Node 1

    创建一个新节点传入了l因此新节点的prev指向Node 1,next为null,并且更新尾节点,把尾节点指向新节点,此时last的值就为Node 2了

    First
    Node 1
    prev: null
    item: e
    next: null
    Node 2
    prev: Node 1
    item: e2
    next: null
    Last
  2. 更新链表结构:

    if (l == null)           // 如果链表为空first = newNode;     // 新节点即为头节点elsel.next = newNode;
    

    此时的l早已不是null了,而是Node1,因此走else的逻辑,将l, 也就是Node 1的next指向新节点,形成一个双向指针

    First
    Node 1
    prev: null
    item: e
    next: Node 2
    Node 2
    prev: Node 1
    item: e2
    next: null
    Last
  3. 更新尾节点和链表长度:新节点成为尾节点,size 自增

此时双向链表的雏形已经形成


接着我们再次add一个新元素e3,依旧是同样的操作:

  1. 新节点创建

    final Node<E> l = last;  // 获取当前尾节点
    final Node<E> newNode = new Node<>(l, e, null)
    last = newNode;          // 更新尾节点
    

    经过e2的插入过程,last此时为Node2,创建新节点时,其prev就为Node2,last 也更新为了Node3

    First
    Node 1
    prev: null
    item: e
    next: Node 2
    Node 2
    prev: Node 1
    item: e2
    next: null
    Node 3
    prev: Node 2
    item: e3
    next: null
    Last
  2. 更新链表结构:

    if (l == null)           // 如果链表为空first = newNode;     // 新节点即为头节点elsel.next = newNode;
    

    依旧执行else,将Node 2 的next指向Node 3,形成双向链表:

    First
    Node 1
    prev: null
    item: e
    next: Node 2
    Node 2
    prev: Node 1
    item: e2
    next: Node 3
    Node 3
    prev: Node 2
    item: e3
    next: null
    Last

linkBefore 方法

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; // 更新后继节点的 previf (pred == null)    // 如果插入位置为头部first = newNode;elsepred.next = newNode; // 更新前驱节点的 nextsize++;modCount++;
}

该方法与尾插法类似,节点的插入位置变成了从头节点位置插入

具体流程:

  1. 确定插入位置的前驱和后继节点
  2. 更新新节点的 prevnext 引用,同时修改前驱和后继节点的指针
  3. 如果插入位置为链表头部或尾部,更新 firstlast 节点

插入到指定位置

add(int index, E element)

add 方法支持在链表的任意位置插入元素:

public void add(int index, E element) {checkPositionIndex(index); // 检查索引范围if (index == size) // 如果插入到链表尾部linkLast(element);elselinkBefore(element, node(index));
}

关键逻辑

  • 边界判断:通过 checkPositionIndex() 检查索引是否越界
  • 插入方式:
    • 如果插入位置为链表尾部,调用 linkLast 方法
    • 如果插入位置在中间,调用 linkBefore 方法

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

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

相关文章

react antd tabs router 基础管理后台模版

在构建 React 后台管理系统时&#xff0c;使用标签页的方式展示路由是一种高效且用户友好的设计模式。这种实现方式通常允许用户在多个页面之间快速切换&#xff0c;并保留页面的状态&#xff0c;类似于浏览器的多标签页功能。 需求分析 1.动态标签页&#xff1a;根据用户的导…

【OpenCV】图像阈值

简单阈值法 此方法是直截了当的。如果像素值大于阈值&#xff0c;则会被赋为一个值&#xff08;可能为白色&#xff09;&#xff0c;否则会赋为另一个值&#xff08;可能为黑色&#xff09;。使用的函数是 cv.threshold。第一个参数是源图像&#xff0c;它应该是灰度图像。第二…

初次使用uniapp编译到微信小程序编辑器页面空白,真机预览有内容

uniapp微信小程序页面结构 首页页面代码 微信小程序模拟器 模拟器页面为空白时查了下&#xff0c;有几个说是“Hbuilder编译的时候应该编译出来一个app.js文件 但是却编译出了App.js”&#xff0c;但是我的小程序结构没问题&#xff0c;并且真机预览没有问题 真机调试 根据defi…

【开源】一款基于SpringBoot 的全开源充电桩平台

一、下载项目文件 下载源码项目文件口令&#xff1a;动作璆璜量子屏多好/~d1b8356ox2~:/复制口令后&#xff0c;进入夸克网盘app即可保存&#xff08;如果复制到夸克app没有跳转资源&#xff0c;可以复制粘贴口令到夸克app的搜索框也可以打开&#xff08;不用点搜索按钮&#…

Ubuntu上使用system()函数运行不需要输入密码

使用system()运行一些终端命令的时候&#xff0c;需要sudo权限&#xff0c;也就是必须输入密码&#xff0c;那么在程序自启动的时候就无法成功启动。如果设置Ubuntu下所有操作都不需要密码&#xff0c;安全性太低&#xff0c;所以我们可以将需要用到的终端指令给予无需输入密码…

【人工智能】人工智能,深度学习与人工神经网络

人工智能 人工智能一、定义与核心要素二、主要方法与技术三、应用领域四、发展前景与挑战五、分类六、研究目标与价值 深度学习定义与核心思想网络结构工作原理关键技术与模型应用领域发展与挑战 人工神经网络一、定义与原理二、基本特性三、网络结构四、工作原理五、应用领域六…

RPC 详解

一、简介 RPC&#xff08;Remote Procedure Call&#xff0c;远程过程调用&#xff09;是一种计算机通信协议&#xff0c;允许程序在不同的计算机上执行过程或服务。RPC 使得开发者能够像调用本地函数一样调用远程服务&#xff0c;简化了网络编程的复杂性。使得开发者能够专注…

WHAT - React 富文本编辑器推荐

目录 1. Draft.js2. Slate.js3. Quill4. TinyMCE5. CKEditor6. react-quill7. TipTap8. Lexical React 生态中有许多优秀的开源富文本编辑器可供使用&#xff0c;以下是一些常用的选择&#xff1a; 1. Draft.js Draft.js 优点&#xff1a; 由 Facebook 开发和维护&#xff0…

php使用file_get_contents返回false

php使用file_get_contents返回false, 具体报错内容如下: failed to open stream: HTTP request failed! HTTP/1.1 400 Bad Request 解决方式1 : 使用curl 替换 file_get_contents 解决方式2 : 请检查请求网址中是否包含中文, 将中文部分进行urlencode function encodeChin…

国产GPU中,VLLM0.5.0发布Qwen2.5-14B-Instruct-GPTQ-Int8模型,请求返回结果乱码

概述 国产GPU: DCU Z100 推理框架&#xff1a; vllm0.5.0 docker容器化部署 运行如下代码&#xff1a; python -m vllm.entrypoints.openai.api_server --model /app/models/Qwen2.5-14B-Instruct-GPTQ-Int8 --served-model-name qwen-gptq --trust-remote-code --enforce…

WireShark速成

1.WireShark安装 官网&#xff1a; Wireshark Go Deep Kali Linux系统自带WireShark工具。 2.WireShark介绍 WireShark是一个网络包分析工具&#xff0c;该工具主要用于捕获网络数据包&#xff0c;并自动解析数据包&#xff0c;为用户显示数据包的详情信息&#xff0c;供…

算法-字符串-72.编辑距离

一、题目 二、思路解析 1.思路&#xff1a; 最少操作数——动态数组 res[i][j]:长度为i的字符串转化为长度为j字符串的最少操作 2.常用方法&#xff1a; 无 3.核心逻辑&#xff1a; 1.情况一&#xff1a;当word1为空&#xff0c;word2不为空时 for(int i0;i<size2;i){res[0…

uniapp-内部项目使用文档

uniapp-内部项目使用文档 目录 uniapp-内部项目使用文档阶段1自行实现内容&#xff1a;阶段1问题记录&#xff1a; 阶段2自行实现内容&#xff1a; 阶段3 APP项目介绍及规范阶段4 公共组件方法UseList 列表页面HooksListItem 列表项uni-load-more 列表加载更多组件CardTitle 列…

大模型在辅导场景的深度应用,猿辅导素养课推出启发性“AI作文通”

猿辅导集团旗下的飞象星球面向学校发布“飞象AI作文”&#xff0c;让教育大模型成为老师的AI批改助手、学生的写作助手。芥末堆注意到&#xff0c;猿辅导集团旗下的猿辅导素养课也推出了名为“AI作文通”的AI作文功能&#xff0c;已于7月正式大规模上线&#xff0c;在AI教育领域…

Node.js系统模块

【图书介绍】《Node.jsMongoDBVue.js全栈开发实战》-CSDN博客 《Node.jsMongoDBVue.js全栈开发实战&#xff08;Web前端技术丛书&#xff09;》(邹琼俊)【摘要 书评 试读】- 京东图书 (jd.com) 2.2.1 什么是系统模块 由于Node.js运行环境提供的API都是以模块化的方式进行开…

路由封装,连接导航router-link

路由的封装抽离&#xff1a; 所有路由配置堆在main.js中不合适&#xff0c;需将路由模块抽离出来&#xff0c;以便维护 将与路由相关信息提取到src文件夹下的router文件夹下的index.js文件中 在main.js中就只需要导入当前路由&#xff0c;并且注入到当前实例中&#xff0c;其他…

工业4.0下的IT网络与OT网络

https://zhuanlan.zhihu.com/p/498984722 随着“中国制造2025”的深入推进&#xff0c;制药行业以手工为主的传统生产方式正在被以“工业4.0 ”为核心的自动化生产方式逐步替代。 为了实现生产自动化&#xff0c;很多制药企业都引入了由PLC&#xff08;可编程逻辑控制器 &am…

Ubuntu压缩打包解压

ubuntu压缩打包 上图&#xff0c;压缩当前目录svn 为svn.tar.gaz&#xff0c;解压后再当前解压目录生成svn文件 在Ubuntu中&#xff0c;你可以使用tar命令来创建一个压缩包&#xff0c;或者使用zip命令来创建一个.zip压缩文件。以下是两种常见的压缩方法&#xff1a; 下图&am…

【FAQ】HarmonyOS SDK 闭源开放能力 —Remote Communication Kit

1.问题描述&#xff1a; DynamicDnsRule有没有示例&#xff1f;这个地址是怎么解析出来 https://developer.huawei.com/consumer/cn/doc/harmonyos-references/remote-communication-rcp-0000001770911890#section8160554134811 解决方案&#xff1a; ‘DynamicDnsRule’&a…

记录c语言一些有趣的疑问

一些有趣的疑问 字符串栈数组调用字符串库API进行赋值么 char szStackStr[] "Hello World!";答案&#xff1a;使用机器指令进行硬编码 无限循环存在比较指令么 while(1) {printf("Hello World!\n"); }答案&#xff1a;while开始处&#xff0c;即使是没…