Java容器源码重点回顾——LinkedList

news2024/11/16 3:21:16

1. 概述

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

LinkedList是实现了List和Deque接口的双端链表。LinkedList的底层数据结构是链表,不支持随机读取,但是在插入和删除方面会比ArrayList来得更高效。因为LinkedList实现了Deque接口,这也是使它具有双端队列的特性。此外,LinkedList不是线程安全的,如果想要使得LinkedList变成线程安全的,可以调用类Collections中的synchronizedList静态方法:

List list = Collections.synchronizedList(new LinkedList<>());

2. 节点类

LinkedList中链表的节点使用的是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;
        }
    }

其实就是我们平常学数据结构的节点类,定义了节点的元素、前驱节点和后继节点。

3. 成员变量

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

    // 头节点指针
    transient Node<E> first;

    // 尾节点指针
    transient Node<E> last;

成员变量比较少,包括节点个数、头节点指针和尾节点指针。

4. 构造方法

    // 空构造方法
    public LinkedList() {
    }

    // Collections参数构造方法
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);  // 添加所有元素
    }

LinkedList的空构造方法是什么也不干,Collections参数构造方法则会调用addAll方法将元素全部添加到LinkedList中,这个方法我们后面会介绍。

5. add方法

    // 指定位置插入元素的add方法
    public void add(int index, E element) {
        checkPositionIndex(index);  // 检查插入位置是否合法

        if (index == size)   // 如果插入位置是链表的末尾
            linkLast(element);  // 调用linkLast方法
        else
            linkBefore(element, node(index)); // 插入位置是链表的中间
    }

    // 查找index位置的元素
    Node<E> node(int index) {
        if (index < (size >> 1)) {  // 如果index在链表的前半部分
            Node<E> x = first;
            for (int i = 0; i < index; i++)  // 从前往后查找
                x = x.next;
            return x;
        } else {  // 如果index在链表的后半部分
            Node<E> x = last; 
            for (int i = size - 1; i > index; i--)  // 从后往前查找
                x = x.prev;
            return x;
        }
    }

    // 插入元素到链表的尾部
    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;  // 将头直接点指向新节点
        else  // 如果不是空链表
            l.next = newNode;  // 原来的尾节点指向新的尾节点
        size++;
        modCount++;
    }

    // 插入元素e到succ之前
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;  // succ的前驱节点
        final Node<E> newNode = new Node<>(pred, e, succ);  // 创建新节点
        succ.prev = newNode;  // 设置succ的前驱为newNode
        if (pred == null)  // 如果前驱节点是null,说明succ原来是头节点
            first = newNode;  // newNode成为新的头节点
        else
            pred.next = newNode;  // 否则,前驱节点的尾节点指向新节点
        size++;
        modCount++;
    }

在指定位置插入元素的add方法,首先是会判断要插入的元素是否是在链表的末尾,如果是的话就直接插入。如果不是插入在链表的末尾,就需要先调用node(index)方法得到原来index位置上的节点,然后再通过linkBefore方法插入到index位置上。

从上面的方法中看到,在LinkedList中完成插入,只需要涉及到index位置上的节点,完全不用移动其他节点,这也是LinkedList的优势,插入非常方便。

6. addAll方法

    //在链表末尾插入Collections
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);  // 检查插入位置是否合法

        Object[] a = c.toArray();  // 将Collections转换成array
        int numNew = a.length;  // array元素的个数
        if (numNew == 0)
            return false;

        Node<E> pred, succ;  // 定义前驱节点和后继节点
        if (index == size) {  // 如果插入的位置是链表末尾
            succ = null;  // 就没有后继节点
            pred = last;  // 前驱节点就是尾节点
        } else {
            succ = node(index);  // 后继就是index位置的节点
            pred = succ.prev;  // 前驱就是index位置的前驱节点
        }

        for (Object o : a) {  // 遍历array中的元素
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);  // 创建节点
            if (pred == null)  // 没有前驱的话
                first = newNode;  // 就是新的头节点
            else
                pred.next = newNode;  // 否则前驱的后继节点是新的节点
            pred = newNode;   // 更新前驱
        }

        if (succ == null) {  // 如果没有后继
            last = pred;
        } else {  // 更新后继节点的状态
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

addAll方法中其实和普通的add方法没有什么区别,只是换成了Collections,里面包含多个元素。需要注意的是,方法首先是会将Collections转化成array,因为array具有随机访问的特点,比较方便。

7. remove方法

    // 删除指定位置元素的remove方法
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index)); 
    }

    // 删除指定位置元素
    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;  // 前驱的后继节点指向next
            x.prev = null; // gc
        }

        if (next == null) {  // 如果没有后继
            last = prev;  // 前驱节点成为新的尾节点
        } else {
            next.prev = prev;  // 后继节点的前驱指向prev
            x.next = null;  // gc
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

remove方法也不难,将当前节点的前驱和后继关联起来,然后将当前节点的前驱和后继置为null即可。如下图所示:
在这里插入图片描述

8. set方法

    public E set(int index, E element) {
        checkElementIndex(index);  // 检查index范围是否合法
        Node<E> x = node(index);  // 去的index位置的节点
        E oldVal = x.item;  // 旧值
        x.item = element;  // 替换新值
        return oldVal;
    }

set方法是链表中修改元素的方法,比较简单。

9. peek和getFirst方法

    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

peek和getFirst方法都能获取链表的队首元素,区别在于如果队列为空,peek元素不会报错,而getFirst方法会报错。

参考文章:
搞懂 Java LinkedList 源码

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

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

相关文章

java TCP接收数据

查看本文 你需要先了解 TCP发送数据 如果没有了解过 可以查看我的文章 java TCP发送数据 然后 我们创建一个包 包下创建两个类 sendOut 客户端类 参考代码如下 import java.io.IOException; import java.io.OutputStream; import java.net.Socket;public class sendOut {publ…

13-Java方法

目录 1.方法的基本用法 1.1.什么是方法 1.2.方法定义语法 1.3.方法调用的执行过程 1.4.实参和形参的关系 1.5.方法的返回值 2.方法重载 2.1.方法重载定义 2.2.代码示例 3.方法递归 3.1.方法递归定义 3.2.方法递归使用条件 3.3.递归与非递归优劣比较 3.4.递归执行…

磷脂PEG衍生物Biotin-PEG-DSPE,生物素PEG磷脂,CAS:385437-57-0

英文名称&#xff1a;Biotin-PEG-DSPE、DSPE-PEG-Biotin 中文名称&#xff1a;生物素-聚乙二醇-磷脂 Item no&#xff1a;X-GF-0068-10k Classification&#xff1a;Biotin PEG DSPE PEG CAS&#xff1a;385437-57-0 MV&#xff1a;可定制&#xff0c;2000、1000、3400、1…

241. 楼兰图腾——树状数组

在完成了分配任务之后&#xff0c;西部 314 来到了楼兰古城的西部。 相传很久以前这片土地上(比楼兰古城还早)生活着两个部落&#xff0c;一个部落崇拜尖刀(V)&#xff0c;一个部落崇拜铁锹(∧)&#xff0c;他们分别用 V 和 ∧ 的形状来代表各自部落的图腾。 西部 314 在楼兰…

xv6 makefile详解

文章目录makefile语法格式生成qemu可执行文件生成kernel可执行文件生成kernel下的OBJSkernel.ldbuild OBJS_KCSANbuild initcode生成一个fs.img文件系统mkfs用户程序的编译配置工具makefile语法格式 makefile就是一个深搜的过程&#xff0c;最上面的语句是顶级目标&#xff0c…

python-函数、文件、异常、模块

目录 函数 返回值 函数传参 位置参数 关键字传参 缺省参数 不定长参数 匿名函数 文件操作 open 函数 异常 模块 导入模块 函数 返回值 return语句[表达式]退出函数&#xff0c;选择性地向调用方返回一个表达式。不带参数值的return语句返回None #定义函数 def a…

Ubuntu系统装机流程(显卡驱动、cuda、cudnn、搜狗输入法、anaconda、pycharm)

整体流程一、安装Ubuntu18.04系统二、安装显卡驱动三、安装Cuda四、安装Cudnn五、安装搜狗输入法六、安装Anaconda七、安装Pycharm社区版一、安装Ubuntu18.04系统 &#xff08;1&#xff09;实现用软碟通做好一个装有Ubuntu18.04的系统盘。 &#xff08;2&#xff09;打开电脑…

Jenkins自动发布到Docker部署服务器把Jar包打包成镜像并启动容器

《jenkins自动化发布到服务器并自动运行》 第1种方法&#xff1a;使用外部Jar包完成自动化部署&#xff08;简单方便&#xff09;&#xff0c;正式环境更新jar包时&#xff0c;备份一下旧的的jar包即可。 修改jenkins项目配置 Pre Steps 构建前清除旧的jar包&#xff0c;然后…

计算机网络原理第2章 物理层

目录 2.1 物理层的基本概念 2.2.1 数据通信系统的模型 2.2.2 有关信号的几个基本概念 1.通信 2.调制 3.编码 2.2.3 信道的极限容量 1.信道能够通过的频率范围&#xff08;奈氏准则&#xff09; 2. 信噪比&#xff08;香农公式&#xff09; 3.奈氏准则与香农公式的比…

Linux操作系统CentOS7安装mysql5.7.x

一、下载mysql5.7.x安装包 &#x1f308; MySQL官方下载&#xff1a;https://dev.mysql.com/downloads/mysql/5.7.html 注意&#xff0c;需要在Windows上解压之后&#xff0c;会有两个压缩包&#xff0c;将其中一个上传 二、将mysql5.7.x安装包上传到Linux服务器 使用 Xftp 上传…

NeurIPS 2022 Spotlight | SNAKE:首个同时进行隐式重建和三维特征点提取的方法

原文链接&#xff1a;https://www.techbeat.net/article-info?id4361 作者&#xff1a;钟程亮 3D特征点检测在物体识别、场景重建等任务中有着重要作用。然而由于点云数据采样的稀疏性&#xff0c;从中检测出3D特征点是一项很有挑战性的任务。虽然原始点云的获取方式有很多种&…

Kafka基础_1

Kafka系列 注&#xff1a;大家觉得博客好的话&#xff0c;别忘了点赞收藏呀&#xff0c;本人每周都会更新关于人工智能和大数据相关的内容&#xff0c;内容多为原创&#xff0c;Python Java Scala SQL 代码&#xff0c;CV NLP 推荐系统等&#xff0c;Spark Flink Kafka Hbase …

一文讲懂泛型

Java高级Java高级语言特性一. 泛型1. 1 为什么我们需要泛型1. 2 泛型类和泛型接口的定义1. 3 泛型方法1. 4 限定类型变量1. 5 泛型中的约束和局限性1. 6 泛型中的继承规则1. 7 通配符类型1.7.1 问题抛出&#xff0c;为啥需要通配符&#xff1f;1.7.2 &#xff1f; extends X1.7…

RocketMq的基本概念

&#x1f3b6; 文章简介&#xff1a;RocketMq的基本概念 &#x1f4a1; 创作目的&#xff1a;关于RocketMq的基本概念的大致介绍 ☀️ 今日天气&#xff1a;阳光明媚。 &#x1f4dd; 每日一言&#xff1a;冬有冬的来意&#xff0c;雪有雪的秘密。 文章目录&#x1f436; 1、Ro…

MySQL~DQL查询数据

4、DQL查询数据&#xff08;最重点&#xff09; 4.1、DQL &#xff08;Data Query LANGUAGE&#xff1a;数据查询语言&#xff09; 所有的查询操作都用它 Select简单的查询&#xff0c;复杂的查询它都能做~数据库中最核心的语言&#xff0c;最重要的语句使用频率最高 SELEC…

Kafka 集群部署与测试

安装Kafka&#xff08;需要JDK和Zookeeper&#xff09;: 下载Kafka安装包&#xff0c;并解压至node01节点中的/opt/apps目录下。修改配置文件。在server.properties配置文件中指定broker编号、Kafka运行日志存放的路径、指定Zookeeper地址和本地IP。添加环境变量。在/etc/prof…

[ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2018-19475

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

【IDEA】# 快速生成logger、通过Maven的profile配置实现环境的快速切换、常用基础设置

1. 快速生成logger 打开 Settings&#xff0c;找到 Editor 目录下的 Live Templates 选中 Java&#xff0c;点击右侧的加号&#xff0c;创建一个新的模板 在创建模板的相关位置&#xff0c;填上对应的值 Abbreviation&#xff1a;触发的关键字&#xff08;此处我使用的是 l…

Postman进阶篇(十二)-在脚本中使用pm对象访问接口响应数据(pm.response.*)

在之前的文章中介绍过postman中的两个脚本——pre-request script或test script&#xff0c;在这两个脚本中都有使用到pm对象。&#xff08;pre-request script详细介绍、Test script详细介绍&#xff09;pm对象是在postman的脚本中非常重要&#xff0c;也是十分常用的方法。本…

SpringCloud学习笔记 - Nacos配置中心搭建 - Nacos Config

Nacos 提供用于存储配置和其他元数据的 key/value 存储&#xff0c;为分布式系统中的外部化配置提供服务器端和客户端支持。使用 Spring Cloud Alibaba Nacos Config&#xff0c;您可以在 Nacos Server 集中管理你 Spring Cloud 应用的外部属性配置。 Spring Cloud Alibaba Nac…