ArrayList底层结构和源码分析笔记

news2025/3/14 22:09:20

参考视频:韩顺平Java集合

image.png

ArrayList特点

  • ArrayList 可以加入 null,包括多个。

  • ArrayList 是由数组来实现数据存储的

  • ArrayList 基本等同于 Vector,除了 ArrayList 是线程不安全(执行效率高)。在多线程情况下,不建议使用 ArrayList

    • 例如 ArrayList 的 add() 的源码:可以发现没有 synchronized 关键词修饰
    public boolean add(E e) {  
        modCount++;  
        add(e, elementData, size);  
        return true;  
    }
    

ArrayList 源码分析(重难点🚩)

  1. ArrayList 中维护了一个 Object 类型的数组 elementData
    transient 意为短暂的;表示该属性不会被序列化

    transient Object[] elementData;
    
  2. 当创建对象时,如果使用的是无参构造器,则初始 elementData 容量为 0 (jdk7是10)

  3. 当添加元素时:先判断是否需要扩容,如果需要扩容,则调用 grow 方法,否则直接添加元素到合适位置

  4. 如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容 elementData 为 10,如果需要再次扩容的话,则扩容 elementData 为 1.5 倍。

  5. 如果使用的是指定容量大小的构造器,则初始 elementData 容量为指定大小

  6. 如果使用的是指定容量大小的构造器,如果需要扩容,则直接扩容 elementData 为 1.5 倍。
    [[ArrayList源码分析]]

源码示例

未指定初始容量

注意将 idea 的调式工具设置一下,以便更好的观察数据:
image.png|575

  • 测试源码:

    • 观察调用构造器,创建 ArrayList 的实例的细节
    • 观察第一次添加元素(初次扩容)
    • 观察第二次添加元素(不用扩容)
    • 观察第十一次添加元素(第二次扩容)
    • 观察第十六次添加元素(第三次扩容)
    @Test  
    public void test1(){  
        ArrayList list = new ArrayList();  
      
        for (int i = 11; i <= 20; i++) {  
            list.add(i);  
        }  
      
        for (int i = 21; i < 26; i++) {  
            list.add(i);  
        }  
      
        list.add(100);  
        list.add(200);  
        list.add(null);  
        System.out.println(list);  
    }
    

创建 ArrayList 实例

  • 创建一个长度为 0 的空数组,并赋给 elementData,elementData 就是集合底层存储数据的数组。
    image.png|650

第一次添加元素“11”

  • 将 int 数据进行装箱
  • 添加这个数据“11”,要保证数组的长度至少为当前元素数+1。(size+1=1)
  • ensureCapacityInternal() 的作用就是检查数组够不够此次添加所需的容量,如果不够,则扩容,更改 elementData ,将其用扩容后的数组替代。
    image.png|600
  • calculateCapacity() 的作用仅仅在于,检查当前操作是否是第一次添加数据。因为第一次添加元素,扩容机制应该将其扩容到 10(除非要添加的元素本身需要的容量大于 10)。
  • 而对于非第一次添加的元素,其所需容量依旧是传进来的参数
    image.png|650
    image.png|700
  • 那么根据判断,此次显然是第一次添加元素,数组为空数组,且所需容量 var1=1,<10。那么此时 var0=10,这就确定了最终数组所需要的容量大小。作为参数传递给 ensureExplicitCapacity() 中。
    image.png|650
  • modCount 为当前集合被修改的次数。需要自增。
  • 最后进行检查:目前数组的容量(elementData.length)是否满足所需要的容量大小(var1)呢?
  • 显然,目前的数组大小为 0,而我们所需要的容量为 10。所以需要扩容。进入到 grow() 中,进行真正的扩容操作。
    image.png|600
  • 所需的容量大小为 var1=10,作为参数传递进来。
  • var2=数组目前的容量==(var2=0)==
  • var3=扩容为当前数组容量的 1.5 倍,不过在初次扩容中,此次操作不起作用==(var3=0)==
  • 判断扩容后的容量大小 var3 是否满足,所需的大小 var1,不满足则依照 var1。(var3=10)
  • 判断扩容后的容量是否在数组的取值范围内。
  • Arrays.copyOf():创建一个指定长度的数组==(10)==的数组,并将原数组中的数据拷贝至该数组,并赋值给 elementData
  • 至此,数组的扩容完成(elementData.length 从 0 扩容到 10)回到 add 的主逻辑中:
    image.png|600
  • 添加元素“11”添加到数组 elementData[0]中,并且 size=1;

第二次添加元素“12”(不用扩容)

  • 将 int 数据进行装箱
  • 添加这个数据“12”,要保证数组的长度至少为当前元素数+1。(size+1=2)
  • ensureCapacityInternal() 的作用就是检查数组是否大于等于 2(够不够此次添加所需的容量)。如果不够,则扩容,更改 elementData ,将其用扩容后的数组替代。
    image.png|600
  • 此时由于不是第一次添加,数组长度不为 0,所以 calculateCapacity() 执行后返回 2
    image.png|600
    image.png|700
  • 集合修改次数+1
  • 当前数组长度==(elementData.length=10)满足所需长度(var1=2)==,所以不需要扩容。
    image.png|550
  • 回到主逻辑中:
    image.png|600
  • elementData[1]存放 var1。size 变为 2。

观察第十一次添加元素(第二次扩容)

  • 此时数组容量为 elementData.length=10,所需容量大小为 var1=11,在这一步时,容量不够,所以需要进行扩容,进入 grow()
    image.png|650
  • var2=10,var3=15(扩容 1.5 倍)
    image.png|600

同理,后面每一次添加和扩容都与以上的步骤一致。

指定初始容量

  • 与未指定的区别在于,调用的是带参的构造器。
  • 指定了初始容量 var1,则创建一个初始容量为 var1 的数组。如果本身为 0,就仍旧赋值为空数组(与无参的一致)
  • 在之后的扩容中,依旧是按照 1.5 倍扩容。
  • 例如指定初始容量为 8,则初次扩容为 8->12
    image.png

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

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

相关文章

[内网渗透] 红日靶场2

环境配置 靶场地址: http://vulnstack.qiyuanxuetang.net/vuln/wiki/ 环境配置可以看这个: https://www.bilibili.com/video/BV1De4y1a7Ps/?spm_id_from333.337.search-card.all.click&vd_sourcecf73ac8de9b7c0322b1bccf77de91c5dNAT模式分配111段, DHCP也要更改 再添加…

MySQL 企业版 TDE加密后 测试和问题汇总

一、测试keyring file 1.1 当keyring file文件丢失或者被篡改 结论&#xff1a;不影响当前正在运行的数据库&#xff0c;但是在重启服务后会启动失败出现报错。 tail -n 100 /var/log/mysql/error.log 报错信息如下&#xff1a; 2025-03-12T08:04:54.668847Z 1 [ERROR] [M…

Unity 封装一个依赖于MonoBehaviour的计时器(下) 链式调用

[Unity] 封装一个依赖于MonoBehaviour的计时器(上)-CSDN博客 目录 1.加入等待间隔时间"永远执行方法 2.修改为支持链式调用 实现链式调用 管理"链式"调度顺序 3.测试 即时方法​编辑 "永久"方法 链式调用 ​4.总结 1.加入等待间隔时间&qu…

套接字缓冲区以及Net_device

基础网络模型图 一般网络设计分为三层架构和五层设计&#xff1a; 一、三层架构 用户空间的应用层 位于最上层&#xff0c;是用户直接使用的网络应用程序&#xff0c;如浏览器、邮件客户端、即时通讯软件等。这些程序通过系统调用&#xff08;如 socket 接口&#xff09;向内核…

2024下半年真题 系统架构设计师 案例分析

案例一 软件架构 关于人工智能系统的需求分析&#xff0c;给出十几个需求。 a.系统发生业务故障时&#xff0c;3秒内启动 XXX&#xff0c;属于可靠性 b.系统中的数据进行导出&#xff0c;要求在3秒内完成&#xff0c;属于可用性 c.质量属性描述&#xff0c;XXX&#xff0c;属…

c++介绍智能指针 十二(2)

智能指针share_ptr,与unique_ptr不同&#xff0c;多个shar_ptr对象可以共同管理一个指针&#xff0c;它们通过一个共同的引用计数器来管理指针。当一个智能指针对象销毁时&#xff0c;计数器减一。当计数器为0时&#xff0c;会将所指向的内存对象释放。 #include<memory>…

西门子S7-1200 PLC远程调试技术方案(巨控GRM532模块)

三步快速实现远程调试 硬件部署 准备西门子S7-1200 PLC、巨控GRM552YW-C模块及编程电脑。GRM552YW-C通过网口与PLC连接&#xff0c;支持4G/5G/Wi-Fi/有线网络接入&#xff0c;无需复杂布线。 软件配置 安装GVCOM3配置软件&#xff0c;注册模块&#xff08;输入唯一序列号与密…

Mac上更改默认应用程序

Mac上为某些文件设置默认打开应用的时候&#xff0c;刚开始是通过打开方式&#xff0c;其他里面&#xff0c;勾选始终以此方式打开&#xff0c;但实际上这个功能并不太好用&#xff0c;经常会让人误以为已经设置好了。但是实际上只是在当前目录起作用。真正解决这个问题可以按照…

【开源+代码解读】Search-R1:基于强化学习的检索增强大语言模型框架3小时即可打造个人AI-search

大语言模型(LLMs)在处理复杂推理和实时信息检索时面临两大挑战:知识局限性(无法获取最新外部知识)和检索灵活性不足(传统方法依赖固定检索流程)。现有方法如检索增强生成(RAG)和工具调用(Tool-Use)存在以下问题: RAG:单轮检索导致上下文不足,无法适应多轮交互场景…

贪心算法和遗传算法优劣对比——c#

项目背景&#xff1a;某钢管厂的钢筋原材料为 55米&#xff0c;工作需要需切割 40 米&#xff08;1段&#xff09;、11 米&#xff08;15 段&#xff09;等 4 种规格 &#xff0c;现用贪心算法和遗传算法两种算法进行计算&#xff1a; 第一局&#xff1a;{ 40, 1 }, { 11, 15…

网络安全防护总体架构 网络安全防护工作机制

1 实践内容 1.1 安全防范 为了保障"信息安全金三角"的CIA属性、即机密性、完整性、可用性&#xff0c;信息安全领域提出了一系列安全模型。其中动态可适应网络安全模型基于闭环控制理论&#xff0c;典型的有PDR和P^2DR模型。 1.1.1 PDR模型 信息系统的防御机制能…

SpringCloud带你走进微服务的世界

认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 单体架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部…

Python设计模式 - 建造者模式

定义 建造者模式是一种创建型设计模式&#xff0c;主要用于构建包含多个组成部分的复杂对象。它将对象的构建过程与表示分离&#xff0c;使得同样的构建过程可以创建不同的对象表示。 结构 抽象建造者&#xff08;Builder&#xff09;&#xff1a;声明创建产品的各个部件的方…

在 Ubuntu 上安装和配置 Docker 的完整指南

Docker 是一个开源的平台&#xff0c;旨在简化应用程序的开发、部署和运行。通过将应用程序及其依赖项打包到容器中&#xff0c;Docker 确保应用程序可以在任何环境中一致地运行。 目录 前言安装前的准备安装 Docker 步骤 1&#xff1a;更新包索引步骤 2&#xff1a;安装必要…

网络安全之数据加密(DES、AES、RSA、MD5)

刚到公司时&#xff0c;我的工作就是为app端提供相应的接口。之前app使用的是PHP接口&#xff0c;对数据加密方面做得比较少。到使用java接口时&#xff0c;老大开始让我们使用DES加密&#xff0c;进行数据传输&#xff0c;但是后来觉得DES是对称加密&#xff0c;密钥存在客户端…

基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 校园周边美食探索及分享平台结构图…

vscode关闭仓库后如何打开

vscode源代码管理->更改->代码 关闭仓库后如何打开。 关闭仓库操作 打开仓库操作 1.按下 Ctrl Shift P&#xff08;Windows/Linux&#xff09;或 Cmd Shift P&#xff08;Mac&#xff09;打开命令面板。 2.在命令面板中输入 Git: Open Repository&#xff0c;然后选…

DeepSeek-R1 论文阅读总结

1. QA问答&#xff08;我的笔记&#xff09; Q1: DeepSeek如何处理可读性问题&#xff1f; 通过构建冷启动数据&#xff08;数千条长CoT数据&#xff09;微调基础模型&#xff0c;结合多阶段训练流程&#xff08;RL训练、拒绝采样生成SFT数据&#xff09;&#xff0c;并优化输…

Linux 》》Ubuntu 18 LTS 之后的版本 修改IP地址 主机名

进入目录 /etc/netplan 修改 50-cloud-init.yaml 》保存文件后&#xff0c;执行以下命令应用更改&#xff1a; sudo netplan apply 》》 DHCP模式 修改主机名 hostnamectl set-hostname xxxx 修改cloud.cfg 防止重启主机名还原 但测试下来 不修改&#xff0c; 重启 也不会还…

泰山派开发之—Ubuntu24.04下Linux开发环境搭建

简介 最近翻到了吃灰已久的泰山派&#xff0c;是刚出来的时候用优惠券买的&#xff0c;当时价格挺便宜的&#xff0c;最近给它翻出来了&#xff0c;打算试试做个项目。买的泰山派容量是2G16G&#xff0c;SOC芯片使用的是RK3566&#xff0c;搭载1TOP算力的NPU&#xff0c;并且具…