【Java进阶之路】LinkedList源码分析

news2024/11/19 3:27:42

概述

LinkedList也是我们经常使用的集合,本文就LinkedList的几个主要方法展开介绍,并结合几个图片来介绍几个重要操作。

基础属性

transient int size = 0;  //节点数量

/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;  //第一个节点(头节点)

/**
 * Pointer to last node.
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;//最后一个节点(尾节点)

//Node的数据结构
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;
    }
}

基本数据结构图如下:

add方法

public boolean add(E e) {
    //调用linkLast方法,将节点添加到尾部
    linkLast(e);
    return true;
}

//在index位置插入节点, 节点值为element
public void add(int index, E element) {
    //校验index是否越界
    checkPositionIndex(index);
    //如果索引为size,即将element插 入链表尾部
    if (index == size)
        //调用linkLast将节点插入链表尾部
        linkLast(element);
     //否则,将element插入原index位置节点的前面,
    //即:将element插入index位置,将原index位置节点移到index+1的位置
    else
        //调用linkBefore插入index位置
        linkBefore(element, node(index));
}

add(E e):调用linkLast方法将元素添加到尾部(linkLast方法详解见下文)
add(int index, E element)

  • 检查index是否越界
  • 比较index与size,如果index==size,则代表插入位置为链表尾部,调用linkLast方法(linkLast方法详解见下文),否则调用linkBefore方法(LinkBefore方法详解见下文)

get方法

public E get(int index) {
    //校验index是否越界
    checkElementIndex(index);
    //根据index,调用node方法寻找目标节点,寻找目标节点的item
    return node(index).item;
}

根据index,调用node方法(见下文node方法详解)寻找目标节点,返回目标节点的item。

node方法

//根据index位置寻找node
Node<E> node(int index) {
    //如果index < size/2, 则代表index在链表的前半部分,从头结点开始遍历
    if (index < (size >> 1)) {
        Node<E> x = first;
        //从first节点遍历,直到index位置
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
        //否则,index在链表的后半部分,从尾节点开始遍历
    } else {
        Node<E> x = last;
        //从last节 点遍历,直到index位 置
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

如果index在链表的前半部,则从头节点开始遍历;否则从尾节点开始遍历。

set方法

//替换index位置节点的值为element
public E set(int index, E element) {
    //检查index是否越界
    checkElementIndex(index);
    //根据index, 调用node方法寻找到目标节点
    Node<E> x = node(index);
    //节点的原值
    E oldVal = x.item;
    //将节点的item属性替换为element
    x.item = element;
    //返回节点原值
    return oldVal;
}

检查index是否越界
调用node方法寻找目标节点(见上文node方法详解)
将目标节点的item属性设为element

remove方法

public boolean remove(Object o) {
    //如果o为空,则遍历链表寻找item属性为空的节点
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            //如果目标节点存在
            if (x.item == null) {
                //则调用unlink方法将该节点移除
                unlink(x);
                return true;
            }
        }
        //如果o不为空, 则遍历链表寻找item属性跟o相同的节点
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            //如果目标节点存在
            if (o.equals(x.item)) {
                //则调用unlink方法将该节点移除
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

//移除index位置的节点
public E remove(int index) {
    //检查index是否越界
    checkElementIndex(index);
    //移除index位置的节点
    return unlink(node(index));
}

remove(Object o)

  • 判断o是否为null,如果o为null,则遍历链表寻找item属性为空的节点,并调用unlink方法将该节点移除(unlink方法详解见下文)
  • 如果o不为null, 则遍历链表寻找item属性跟o相同的节点,并调用unlink方法将该节点移除(unlink方法详解见下文)

remove(int index)

  • 检查index是否越界
  • 调用unlink方法,移除index位置的节点(unlink方法详解见下文)

clear方法

//清除链表的所有节点
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; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    //将头节点和尾节点设置为null
    first = last = null;
    //size清零
    size = 0;
    modCount++;
}

从first节点开始,遍历将所有节点的属性清空
将first节点和last节点设为null

linkLast方法

//将e放到链表的最后一个节点
  void linkLast(E e) {
      //拿到当前的尾节点l节点
      final Node<E> l = last;
      //使用e创建一个新的节点newNode, prev属性为l节点,next 属性为null
      final Node<E> newNode = new Node<>(l, e, null);
      //将当前尾节点设置为上面新创建的节点newNode
      last = newNode;
      //如果l节点为空则代表当前链表为空,将newNode设置为头结点
      if (l == null)
          first = newNode;
      //否则将l节点的next属性设置为newtNode
      else
          l.next = newNode;
      size++;
      modCount++;
  }
  • 拿到当前的尾节点 l 节点
  • 使用e创建一个新的节点newNode,prev属性为l节点,next属性为null
  • 将当前尾节点设置为上面新创建的节点newNode
  • 如果l节点为空则代表当前链表为空, 将newNode设置为头结点,否则将l节点的next属性设置为newNode

过程如图所示

linkBefore方法

// 将节点e插入节点succ前面
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    //拿到succ节点的prev节点,赋值给pred节点
    final Node<E> pred = succ.prev;
    //使用e创建一个新的节点newNode, 其中prev属性为pred节点,next属性为succ节点
    final Node<E> newNode = new Node<>(pred, e, succ);
    //将succ 节点的prev属性设置为newNode
    succ.prev = newNode;
    //如果pred节点为null,则代表succ 节点为头结点,
    //要把e插入succ前面,只 需将first设置为newNode
    if (pred == null)
        first = newNode;
     //否则将pred节点的next属性设为newNode
    else
        pred.next = newNode;
    size++;
    modCount++;
}
  • 拿到succ节点的prev节点
  • 使用e创建一个新的节点newNode,其中prev属性为pred节点,next属性为succ节点
  • 将succ节点的prev属性设置为newNode
  • 如果pred节点为null,则代表succ节点为头结点,要把e插入succ前面,因此将first设置为newNode,否则将pred节点的next属性设为newNode

过程如图所示

unlink方法

//移除链表上的x节点
E unlink(Node<E> x) {
    // x节点的值
    final E element = x.item;
    // x节点的下一个节点next节点
    final Node<E> next = x.next;
    // x节点的上一个节点prev节点
    final Node<E> prev = x.prev;
    //如果prev为空, 则代表x节点为头结点,则将first指向next即可
    if (prev == null) {
        first = next;
        //否则,x节点不为头结点,
    } else {
        //将prev节点的next属性指向x节点的next属性
        prev.next = next;
        //将x的prev属性清空
        x.prev = null;
    }

    如果next为空,则代表x节点为尾节点,则将last指向prev即可
    if (next == null) {
        last = prev;
        //否则,节点不为尾节点
    } else {
        //将next节点的prev属性指向x节点的prev属性
        next.prev = prev;
        //将x的next属性清空
        x.next = null;
    }
    //将x的值清空,以便垃圾收集器回收x对象
    x.item = null;
    size--;
    modCount++;
    return element;
}
  • 定义element为x节点的值,next为x节点的下一个节点,prev为x节点的上一个节点
  • 如果prev为空,则代表x节点为头结点,则将first指向next即可;否则,x节点不为头结点,将prev节点的next属性指向x节点的next属性,并将x的prev属性清空
  • 如果next为空,则代表x节点为尾节点,则将last指向prev即可;否则,x节点不为尾节点,将next节点的prev属性指向x节点的prev属性,并将x的next属性清空
  • 将x的item属性清空,以便垃圾收集器回收x对象

过程如图所示

来自:LinkedList详解

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

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

相关文章

windows上安装Vmware及Linux系统

Linux系统的安装 一、windows上安装Vmware 第一步&#xff1a;复制VMware软件包到Windows系统中 第二步&#xff1a;双击VMware安装包&#xff0c;进行软件的安装 第三步&#xff1a;勾选软件的许可协议 第四步&#xff1a;设置VMware安装路径以及勾选增强型的键盘程序 第五步…

Three.js环境光,平行光,点光源,聚光灯的创建和灯光辅助线的使用

Three.js中的灯光API使用 1.环境光&#xff08;AmbientLight&#xff09;2.平行光&#xff08;directionalLight&#xff09;3.PointLight(点光源) 4.聚光灯&#xff08;SpotLight&#xff09;5.材质平面&#xff08;PlaneGeometry&#xff09;用于接收&#xff08;平行光和聚…

【边缘计算】【第一章 什么是边缘计算】

边缘计算 序第一章 什么是边缘计算概念章鱼说应用场景数据单位转换边缘计算的前世今生CDN&#xff08;Content Delivery Network&#xff09;内容分发网络微云&#xff08;Cloudlet&#xff09;雾计算——雾是接近地面的云MEC边缘计算大事记 边缘计算核心技术概述1 网络技术2 隔…

explain 是干嘛的

explain 是干嘛的 1.explain的作用 在MySQL中&#xff0c;EXPLAIN是一个用于查询优化的关键字。它可以用于分析查询语句的执行计划&#xff0c;帮助开发人员和数据库管理员理解查询的执行方式、查询涉及的表和索引、连接类型、查询优化器的决策等信息。 通过使用EXPLAIN关键…

智慧团建登录或忘记密码刷不出验证码

问题如下&#xff1a; 忘记密码和登录时没有验证码 原因&#xff1a;智慧团建的服务器端只放行不带“www.”的域名&#xff0c;一般zf或者其他jg系统都会限制万维网的进入 解决办法&#xff1a; 删掉“www.”&#xff0c;然后重新回车访问或者直接点我下边的链接&#xff1a;…

PyTorch翻译官网教程6-AUTOMATIC DIFFERENTIATION WITH TORCH.AUTOGRAD

官网链接 Automatic Differentiation with torch.autograd — PyTorch Tutorials 2.0.1cu117 documentation 使用TORCH.AUTOGRAD 自动微分 当训练神经网络时&#xff0c;最常用的算法是方向传播算法。在该算法中&#xff0c;根据损失函数与给定参数的梯度来调整模型参数&…

机器学习---定义、用途、算法的分类、假设空间与归纳偏好、奥卡姆剃刀原则

1. 机器学习的定义 基于历史经验的&#xff0c;描述和预测的理论、方法和算法。 从历史数据中&#xff0c;发现某些模式或规律&#xff08;描述&#xff09;&#xff0c;利用发现的模式和规律进行预测。 2. 机器学习能做什么 机器学习已经有了十分广泛的应用&#xff0c;例…

pdf文件大小如何压缩?pdf文件怎么压缩得更小?

日常生活和工作中&#xff0c;经常用到图片&#xff0c;但是有时候需要将图片压缩指定大小来符合各种规定&#xff0c;比如图片压缩到200kb&#xff0c;那么有没有简单方便的图片压缩&#xff08; https://www.yasuotu.com/imagesize&#xff09;的方法呢&#xff1f;下面就拿压…

【测试开发】案例分析

目录 一. 模拟弱网 二. 接口测试 三. 对冒泡排序进行测试 四. 对于 Linux 命令进行测试 五. 微信发送朋友圈设计测试用例 六. 补充 一. 模拟弱网 模拟弱网环境可以借助 Fiddler 来进行&#xff1b; 1. 先要打开 Simulate Modem Speeds 选项&#xff1b; 2. 打开 Customize R…

一起学SF框架系列5.8-模块Beans-注解bean解析1-解析入口

前面跟踪了Spring框架如何解析xml模式配置的bean解析&#xff08;参见“一起学SF框架系列5.7-模块Beans-BeanDefinition解析”&#xff09;&#xff0c;本文主要解析注解bean&#xff08;详见“一起学SF框架系列5.2-模块Beans-bean的元数据配置”&#xff09;是如何被Spring框架…

scripy其他

持久化 # 爬回来&#xff0c;解析完了&#xff0c;想存储&#xff0c;有两种方案 ## 方案一&#xff1a;一般不用 parse必须有return值&#xff0c;必须是列表套字典形式--->使用命令&#xff0c;可以保存到json格式中&#xff0c;csv中scrapy crawl cnblogs -o cnbogs.j…

IEEE WCCI-2020电动汽车路由问题进化计算竞赛的基准集

引言 交通一直是二氧化碳排放的主要贡献者。由于全球变暖、污染和气候变化&#xff0c;联邦快递、UPS、DHL和TNT等物流公司对环境变得更加敏感&#xff0c;他们正在投资于减少作为其日常运作的一部分而产生的二氧化碳排放的方法。毫无疑问&#xff0c;使用电动汽车&#xff08;…

JavaWeb——Linux的常用命令

目录 一、Linux优点 二、Linux常用命令 1、ls &#xff08;1&#xff09;、语法 &#xff08;2&#xff09;、功能 &#xff08;3&#xff09;、常用选项 例: 2、pwd &#xff08;1&#xff09;、语法 &#xff08;2&#xff09;、功能 例: 3、cd &#xff08;1&am…

Doc as Code (1):起源

作为技术传播从业者&#xff0c;你一定听说过Doc as Code&#xff0c;中文大家叫做文档代码化。 近年来&#xff0c;这个词在技术传播行业传开了。也许是在某个大会上&#xff0c;也许是在某篇文章中&#xff0c;再或者是在与同行的讨论群里&#xff0c;不管是从哪里&#xff…

DAY47:动态规划(九)完全背包理论基础

文章目录 完全背包示例与01背包的区别&#xff1a;遍历顺序常规遍历写法DP状态图-为什么背包正序就能放进来重复物品 for循环的嵌套&#xff0c;外层物品内层背包能否颠倒&#xff1f;for嵌套顺序颠倒的遍历写法 测试示例面试题目总结 课程链接&#xff1a; 代码随想录 (progr…

自动生成spring-configuration-metadata.json文件

在开发过程中为避免重复修改代码&#xff0c;往往将代码中容易发生变更的值提取出来放到配置文件中。例如数据库连接信息&#xff0c;使用Http调用第三方应用的网关地址等信息。 使用Sprin Boot的ConfigurationPropertie 从配置文件中读取属性值方法多样&#xff0c;这里介绍…

【反向代理】反向代理及其作用

反向代理及其作用 一、什么是正向代理 在介绍反向代理之前我们先介绍什么是正向代理 首先要明确的是&#xff0c;在http协议中正向代理一般被称为代理&#xff0c;在web服务中我们可以通过主动配置代理服务器的方式来发送请求&#xff0c;并通过代理服务器接收服务器的响应。…

自学网络安全(成为黑客)

一、前言 黑客这个名字一直是伴随着互联网发展而来&#xff0c;给大家的第一印象就是很酷&#xff0c;而且技术精湛&#xff0c;在网络世界里无所不能。目前几乎所有的公司企业甚至国家相关部门都会争相高薪聘请技术精湛的黑客作为互联网机构的安全卫士&#xff0c;所以黑客也…

umi框架的使用

umi框架的使用 安装npm i -g yrm 查看yarn镜像源yrm ls 切换源 yrm use taobao 创建项目 yarn create umijs/umi-app 安装依赖yarn 启动项目yarn start 路由组件还可以进行children进行子路由渲染 打个比方&#xff0c;现在有头部导航跟侧边是一致的我们只希望修改每个应…

Mybatis-Plus详解

目录 一、Mybatis-Plus简介 &#xff08;一&#xff09;什么是Mybatis-Plus &#xff08;二&#xff09;Mybatis-Plus的优势 &#xff08;三&#xff09;Mybatis-Plus的框架结构 二、SpringBoot整合Mybatis-Plus入门 &#xff08;一&#xff09;创建maven工程&#xff0c;…