当前位置: 首页 > ops >正文

ArrayList源码分析

1. ArrayList默认初始化容量

首先编写一个简单的初始化ArrayList的代码

List<String> li = new ArrayList<>();

然后进入ArrayList中,在无参数构造方法中可以查看到上面的绿色注释中写了构造一个空的集合并且初始化容量为10。接下来继续查看源码,分析这个10是如何来的。

在这里插入图片描述

这里面的DEFAULTCAPACITY_EMPTY_ELEMENTDATA可以查看到在调用无参数构造方法时,默认的初始化容量为0。

在这里插入图片描述

当第一次向这个集合中添加元素

li.add("a");

这个时候debug去跟踪一下这个添加元素的过程。首先进入的是add()方法

在这里插入图片描述

然后接着进入下一步,就进入到了这个add()方法中,此时注意到这个s == elementData.length,条件为true,因此进入到下面的grow()方法中

在这里插入图片描述

第一次进入的这个grow()方法不是最终的,还需要看return后面的grow()方法

在这里插入图片描述

进入到这里的grow()方法之后,可以看到oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个条件式值为false,因此走到下面return语句。可以看到在这里会新创建一个数组。这个时候容量为DEFAULT_CAPACITY和minCapacity中的最大值,可以看到此时DEFAULT_CAPACITY的值为10,而minCapacity的值为1,因此最大值即为10。

在这里插入图片描述

在这里插入图片描述

所以得到的结论是,在第一次创建ArrayList数组时,这个时候的容量为0,在第一次调用add()方法添加元素时,才会初始化容量为10。

2. ArrayList扩容策略

List<String> li = new ArrayList<>();
li.add("a1");
li.add("a2");
li.add("a3");
li.add("a4");
li.add("a5");
li.add("a6");
li.add("a7");
li.add("a8");
li.add("a9");
li.add("a10");
//添加第11个元素
li.add("a11");

通过上面的分析,得知了ArrayList的初始化容量为10,那么当添加进入第11个元素时,此时的容量超出了10,这个时候ArrayList就需要进行扩容的操作, 继续对源码进行分析,查看ArrayList是如何进行扩容的。
上面的步骤还是一样,但是到这里的时候,oldCapacity的值为10,因此oldCapacity > 0为true,因此进入到条件里面。这个时候查看到newLength()方法里有三个参数,oldCapacity,minCapacity - oldCapacity和oldCapacity >> 1。oldCapacity为原容量,值为10;minCapacity - oldCapacity为最小增长,值为1;oldCapacity >> 1为预期增长,值为5。

在这里插入图片描述

接下来进入newLength()方法,这个时候预期长度prefLength = oldLength + Math.max(minGrowth, prefGrowth),oldLength即为原先容量10,然后再加上最小增长minGrowth和预期增长prefGrowth中的最大值,可以看到,此时的最大值为5。因此预期的长度即为10 + 5 = 15。

在这里插入图片描述

确定好预期长度为15后,又回到上一步,这个时候会进行数组的扩容。扩容的方式为将原先的元素和新的容量15传入到copyOf()方法中。

在这里插入图片描述

所以总的来说,ArrayList的扩容策略为先创建一个新的数组,新数组的容量为原先容量的1.5倍,然后再将原先的元素进行拷贝。

3. ArrayList的添加、修改、插入和删除元素

3.1 添加元素

添加元素在上面分析初始化容量和扩容策略时已经大概的分析过了,这里就不细说了。

3.2 修改元素

List<String> li = new ArrayList<>();
li.add("a1");
li.add("a2");
li.add("a3");
li.add("a4");
li.add("a5");
li.add("a6");
li.add("a7");
li.add("a8");
li.add("a9");
li.add("a10");li.set(1, "a22");

在修改元素时,由于传入了下标,因此会先对下标是否越界做出判断,如果没有越界则先将原先的值赋值给oldValue,再将新值赋值给指定下标的元素,最后返回原先的元素oldValue。这样修改元素的操作也就完成了。

在这里插入图片描述

3.3 插入元素

List<String> li = new ArrayList<>();
li.add("a1");
li.add("a2");
li.add("a3");
li.add("a4");
li.add("a5");
li.add("a6");
li.add("a7");
li.add("a8");
li.add("a9");
li.add("a10");li.add(1, "a22");

在进行插入元素时,和上面一样,依旧是先进行下标是否越界的判断,如果没有越界,则进行下一步。在执行插入操作的时候,注意红框中的方法,是进行了一个拷贝的操作。
就是从要插入元素的位置开始,比如现在需要插入的位置为1,则从1开始一直到后面所有的元素都进行拷贝,然后从插入元素的位置的下一个位置,也就是第2个位置,将拷贝的那些元素粘贴进去。最后再将需要插入的值赋值给空出来的这个位置,也就是1这个位置。

在这里插入图片描述

3.4 删除元素

List<String> li = new ArrayList<>();
li.add("a1");
li.add("a2");
li.add("a3");
li.add("a4");
li.add("a5");
li.add("a6");
li.add("a7");
li.add("a8");
li.add("a9");
li.add("a10");li.remove(1);

在执行删除元素操作的时候,检测完下标没有越界之后,会先将数组赋值给临时变量es,然后进入fastRemove()方法。

在这里插入图片描述

进入到这个方法后发现,其实在删除元素的时候,也是进行了一个拷贝的操作。比如我现在要删除i = 1这个位置的元素,那么就从i + 1也就是2这个位置开始的元素进行拷贝,然后复制到新的数组中,在新的数组中从1开始往后面赋值。最后再将这个新数组的最后一个元素置为空。

在这里插入图片描述
总的来说,不管是插入元素还是删除元素,都需要将这个元素之后的所有元素进行一个整体的挪动,因此ArrayList在进行插入和删除元素的时候,效率不是很高。

http://www.xdnf.cn/news/7657.html

相关文章:

  • 实现商品列表
  • 建站系统哪个好?
  • 基于CATIA参数化圆锥建模的自动化插件开发实践——NX建模之圆锥体命令的参考与移植(二)
  • 笔记:显示实现接口如何实现,作用是什么
  • ollama部署模型
  • 工单派单应用:5 大核心功能提升协作效率
  • Ai学习之LangChain框架
  • STM32外设应用详解——从基础到高级应用的全面指南
  • 差分数组:原理与应用
  • 文献分享-临床预测模型-基于围手术期时间数据肝切除术后肝衰竭早期检测
  • CSS 背景全解析:从基础属性到视觉魔法
  • MinIO集群故障,其中一块driver-4异常
  • 网络安全之带正常数字签名的后门样本分析
  • 软件测试之环境搭建及测试流程
  • 见多识广10:大模型的一些基础概念
  • Python训练营打卡——DAY31(2025.5.20)
  • 类和对象------2
  • Leetcode百题斩-字典树
  • MySQL 安全更新大量数据
  • MySQL高可用之ProxySQL + MGR 实现读写分离实战
  • 面向AI研究的模块化即插即用架构综述与资源整理全覆盖
  • 数据库实验——备份与恢复
  • 【普及−】洛谷P1862 ——输油管道问题
  • 【latex】文本颜色修改
  • 【QT】QTableWidget获取width为100,与真实值不符问题解决
  • C++ 网络编程(9)字节序处理和消息队列的控制
  • 缺乏进度跟踪机制,如何掌握项目状态?
  • MyBatis常用方法
  • 零售EDI:Belk Stores EDI需求分析
  • 阅读笔记---城市计算中用于预测学习的时空图神经网络研究综述