ArrayList 源码解析和设计思路
ArrayList
- 一、继承体系
- 二、接口继承
- 三、标记接口
- 四、设计目的
- 五、框架总体结构
- 六、工作原理
- 七、创建List对象初始化?还是add()添加元素初始化?
- 七、add(E e)添加元素
- 八、remove(int index)删除元素
- 八、线程安全问题
一、继承体系
ArrayList继承自AbstractList,AbstractList实现了List接口的部分方法,为List的具体实现类提供了方便。AbstractList是一个抽象类,它实现了Collection接口,也就间接实现了Iterable接口。
二、接口继承
List接口继承自Collection接口,Collection继承自Iterable接口。List接口代表有序的队列
三、标记接口
RandomAccess是一个标记接口,标识ArrayList支持快速随机访问,可以通过索引直接访问元素。Cloneable和Serializable也是标记接口,前者表示可复制克隆,后者表示可序列化。
四、设计目的
- Java 集合框架的设计目的是为了更好地对集合数据进行操作,统一了访问方式,增强了代码的可读性、简洁性和可扩展性。
- 使用接口和抽象类来定义规范,然后由具体实现类来实现不同的数据结构和算法。
- 标记接口则用来对类型进行标记和区分,以便采取不同的算法和优化。
五、框架总体结构
- 最顶层是
Collection接口,定义了集合的基本操作。 - 然后分为
List、Set、Queue等子接口,分别定义了有序、无序、队列等特性。 - 再通过
AbstractList、AbstractSet等抽象类为子接口提供部分方法实现。 - 最后是具体的实现类,如
ArrayList、LinkedList、HashMap等。
六、工作原理
ArrayList 底层是通过动态数组(可自动扩容)实现的,它的工作原理如下:
- 初始化时,为一个初始容量为0的数组
- 当添加元素时,容量不够则自动扩容(默认扩容为原来的1.5倍)
- 插入和访问元素通过索引直接操作数组,性能较高
- 删除元素需要移动其他元素以保证连续存储
- 由于自动扩容,在末尾插入元素效率很高,但在中间位置插入效率较低
ArrayList 的源码注释如下:
/*** 默认初始容量为 10*/
private static final int DEFAULT_CAPACITY = 10;/*** 用于储存元素的数组缓冲区*/
transient Object[] elementData; /*** ArrayList 中元素的数量*/
private int size;// 其他方法...
可以看出,ArrayList 内部通过一个 Object 数组存储元素,size 变量记录当前元素数量,通过自动扩容机制来支持动态增长。这种基于动态数组的实现,决定了 ArrayList 查询快、增删慢的特点。
七、创建List对象初始化?还是add()添加元素初始化?
在 Java JDK 1.8 中,ArrayList 的默认初始容量 10 是在第一次通过无参构造函数创建 ArrayList 对象时初始化的,而不是在添加第一个元素时初始化。
ArrayList 有三种构造方式:
-
ArrayList()/*** 构造一个初始容量为10的空列表*/ public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }使用无参构造函数时,底层会初始化一个初始容量为 10 的空数组
DEFAULTCAPACITY_EMPTY_ELEMENTDATA。 -
ArrayList(int initialCapacity)/*** 构造一个具有指定初始容量的空列表*/ public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);} }使用这个指定容量的构造函数,会根据指定的容量初始化底层数组大小。
-
ArrayList(Collection<? extends E> c)/*** 构造一个包含指定collection的元素的列表,按迭代器返回元素的顺序*/ public ArrayList(Collection<? extends E> c) {elementData = c.toArray();if ((size = elementData.length) != 0) {// c.toArray可能(错误地)不同时使用Object[]...if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);} else {// 使用空数组替换以允许缓冲区进行正常增长this.elementData = EMPTY_ELEMENTDATA;} }使用这个构造函数会根据提供的集合的大小创建底层数组。
使用无参构造函数 ArrayList() 时,底层数组的初始容量才会被初始化为 10。其他两种构造函数要么根据用户指定的容量初始化,要么根据提供的集合大小初始化。而添加元素时则不会再初始化容量,只是在容量不足时按照需求扩容。
七、add(E e)添加元素
public boolean add(E e) {ensureCapacityInternal(size + 1); // Increments modCount!!elementData[size++] = e;return true;}
-
ensureCapacityInternal(size + 1): 这一行代码调用了ensureCapacityInternal方法,它用于确保ArrayList内部数组的容量足够来存放新增的元素。如果需要增加ArrayList的容量,它会创建一个新的更大的数组,并将原来的元素复制到新数组中。 -
elementData[size++] = e;: 这一行代码将新的元素e添加到ArrayList的内部数组elementData中,并且将size变量递增,表示列表中的元素数量增加了一个。这里使用size++是因为要先将元素添加到size所指示的位置,然后再递增size,以便下次添加元素时能够添加到正确的位置。 -
return true;: 最后,方法返回true,表示添加操作成功。
private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);}
这段代码是ArrayList中的ensureCapacityInternal(int minCapacity)方法的部分源码
-
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {: 这一行代码用于检查elementData是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,该常量表示一个空数组。如果是空数组,说明当前ArrayList没有初始化容量,需要根据默认容量和需要添加的最小容量来确定实际容量。 -
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);: 如果elementData是空数组,则会根据默认容量DEFAULT_CAPACITY和需要添加的最小容量来确定实际的最小容量。DEFAULT_CAPACITY是ArrayList默认的初始容量大小。 -
ensureExplicitCapacity(minCapacity);: 接下来调用ensureExplicitCapacity(minCapacity)方法来确保ArrayList的内部数组容量能够容纳至少minCapacity个元素。这个方法会比较当前数组的容量和需要的最小容量,如果当前容量不足,则会扩大内部数组的容量以满足需求。
private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity);}
-这段代码是ArrayList中的ensureExplicitCapacity(int minCapacity)方法的部分源码
-
modCount++;: 这一行代码递增了modCount变量的值。modCount用于记录结构修改次数,主要用于在迭代过程中检测并发修改。每当进行结构性修改时(如添加或移除元素),modCount会递增,表示ArrayList的结构发生了变化。 -
if (minCapacity - elementData.length > 0) grow(minCapacity);: 这里使用了溢出安全的计算方式来判断是否需要扩容内部数组。如果当前容量不足以容纳最小容量minCapacity个元素,则需要调用grow(minCapacity)方法来扩大内部数组的容量。 -
grow(int minCapacity): 当需要扩容时,会调用grow(int minCapacity)方法来实现容量的扩充。grow()方法会根据需要的最小容量来确定新的容量大小,并将原来的元素复制到新的更大的数组中。
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);}
这段代码是ArrayList中的grow(int minCapacity)方法的部分源码
-
int oldCapacity = elementData.length;: 获取当前内部数组的容量,即原来的容量大小。 -
int newCapacity = oldCapacity + (oldCapacity >> 1);: 原来的容量大小计算出新的容量大小。新的容量大小会是原来容量的1.5倍,通过右移一位实现了除以2的操作。原来容量大小 + 容量大小的一半 -
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;: 如果新的容量仍然小于需要的最小容量minCapacity,则将新的容量调整为minCapacity,确保容量能够满足需求。 -
if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);: 检查新的容量是否超出了最大数组大小MAX_ARRAY_SIZE,如果超出则调用hugeCapacity()方法来确定合适的容量大小,这是为了避免数组大小超出Java虚拟机的限制。 -
elementData = Arrays.copyOf(elementData, newCapacity);: 最后,使用Arrays.copyOf()方法将原来的元素复制到新的更大的数组中,完成了内部数组的扩容过程。
grow()方法主要负责处理ArrayList内部数组的扩容问题。它会根据旧的容量大小计算出新的容量大小,并根据需要调整为满足最小容量要求的合适大小。然后将原来的元素复制到新的更大的数组中,完成数组的扩容操作。这样可以确保在需要添加大量元素时,ArrayList内部数组能够容纳足够多的元素。
八、remove(int index)删除元素
public E remove(int index) {rangeCheck(index);modCount++;E oldValue = elementData(index);int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its workreturn oldValue;}
这段代码是ArrayList中的remove(int index)方法的部分源码
-
rangeCheck(index): 这一行代码调用了rangeCheck方法来检查索引是否越界,确保要移除的元素在有效范围内。如果索引不在有效范围内,会抛出IndexOutOfBoundsException。 -
modCount++: 这里递增了modCount变量的值。modCount用于记录结构修改次数,主要用于在迭代过程中检测并发修改。每当进行结构性修改时(如添加或移除元素),modCount会递增,表示ArrayList的结构发生了变化。 -
E oldValue = elementData(index): 这一行代码获取要移除的元素,并将其保存为oldValue。 -
int numMoved = size - index - 1;: 计算需要向前移动的元素个数,即原始数组中位于被删除元素后面的元素个数。 -
System.arraycopy(elementData, index+1, elementData, index, numMoved): 如果存在需要向前移动的元素,则使用System.arraycopy方法将这些元素向前移动,以填补被删除元素的位置。 -
elementData[--size] = null: 将列表中最后一个元素置空,以便让GC进行清理工作。 -
返回
oldValue:返回被删除的元素的值。
通过以上步骤,remove()方法实现了在指定位置上删除元素的功能。它首先进行了参数的合法性检查,然后递增了modCount,接着移动需要向前移动的元素,将列表中最后一个元素置空,并返回被删除的元素的值。
public boolean remove(Object o) {if (o == null) {for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;}
这段代码是ArrayList中的remove(Object o)方法的部分源码
-
如果传入的对象
o为空(null):- 通过 for 循环遍历
elementData数组,寻找为 null 的元素。 - 当找到第一个为 null 的元素后,调用
fastRemove(index)方法进行删除,并返回 true。
- 通过 for 循环遍历
-
如果传入的对象
o不为空(non-null):- 通过 for 循环遍历
elementData数组,寻找与传入对象相等的元素。 - 当找到第一个与传入对象相等的元素后,调用
fastRemove(index)方法进行删除,并返回 true。
- 通过 for 循环遍历
-
如果未找到符合条件的元素,则直接返回 false。
fastRemove(index) 方法用于快速删除指定索引位置的元素。通过这种方式,remove(Object o) 方法能够在列表中移除满足特定条件的第一个元素,并返回是否成功移除的布尔值。
八、线程安全问题
ArrayList存在线程安全问题的本质在于其内部的elementData、size和modCount等变量,在进行各种操作时没有加锁,并且这些变量的类型也不是volatile的。因此,如果多个线程对这些变量进行操作,则可能出现值被覆盖的情况。需要强调的是,只有当ArrayList作为共享变量时,才会出现线程安全问题;当ArrayList是方法内的局部变量时,则不存在线程安全问题。
相关文章:
ArrayList 源码解析和设计思路
ArrayList 一、继承体系二、接口继承三、标记接口四、设计目的五、框架总体结构六、工作原理七、创建List对象初始化?还是add()添加元素初始化?七、add(E e)添加元素八、remove(int index)删除元素八、线程安全问题 一、继承体系 ArrayLis…...
Win10系统使用IIS服务搭建WebDAV网站结合内网穿透公网访问本地文件
文章目录 推荐1. 安装IIS必要WebDav组件2. 客户端测试3. cpolar内网穿透3.1 打开Web-UI管理界面3.2 创建隧道3.3 查看在线隧道列表3.4 浏览器访问测试 4. 安装Raidrive客户端4.1 连接WebDav服务器4.2 连接成功4.2 连接成功总结: 推荐 前些天发现了一个巨牛的人工智能…...
AWTK 开源串口屏的配置文件
配置文件 每个 HMI 应用程序都需要一个配置文件,用于配置 HMI 的基本信息、服务、持久化、告警信息、历史数据等。 文件位置 design/default/data/settings.json基本配置 name - 名称(必须配置,只能用字母、数字、下划线) se…...
Spring、SpringMVC、Spring Boot常见注解有哪些?不要混淆了哦
Spring、SpringMVC、Spring Boot常见注解 一、Spring 注解说明Component、Controller、Service、Repository使用在类上用于实例化BeanAutowired使用在字段上用于根据类型依赖注入Qualifier结合Autowired一起使用用于根据名称进行依赖注入Scope标注Bean的作用范围Configuratio…...
在notion里面实现四象限清单
四象限清单是一种时间管理工具,旨在帮助人们根据任务的重要性和紧急性来优先排序他们的工作。这个概念最早由德怀特艾森豪威尔提出,后来又被史蒂芬柯维在他的著作《高效能人士的七个习惯》中进一步普及。四象限清单将任务分为四个类别: 第一…...
【linux】搜索所有目录和子目录下的包含.git的文件并删除
一、linux命令搜索所有目录和子目录下的包含.git的文件 在Linux系统中,要搜索所有目录和子目录下的包含.git的文件,可以使用find命令。find命令允许指定路径、表达式和操作来查找文件。 以下是使用find命令搜索包含.git的文件的方法: 1. 基…...
三、传输层拥塞控制、差错控制
3.1 概述和传输层服务 传输服务和协议: 为运行在不同主机上的应用进程提供逻辑通信; 传输协议运行在端系统-发送方:将应用层的报文分成报文段,然后传递给网络层;接收方:将报文段重组成报文,然后传递给应用…...
主流电商平台数据大规模数据采集接口的实现:电商API接口接入方案和电商数据采集现状
现实问题 1、您是否需要经常统计关注的品牌、产品、平台、卖家的电商数据,包括销量、评价量、收藏量、预售量、运费、赠品和促销信息,手头上没有稳定的数据源? 2、您是否经常需要统计授权卖家和非授权卖家的销售、动销占比,分析…...
Python电梯楼层数字识别
程序示例精选 Python电梯楼层数字识别 如需安装运行环境或远程调试,见文章底部个人QQ名片,由专业技术人员远程协助! 前言 这篇博客针对《Python电梯楼层数字识别》编写代码,代码整洁,规则,易读。 学习与应…...
Linux学习:基础开发工具的使用(1)
目录 1. Linux软件包管理器:yum工具1.1 yum是什么(软件商城)1.2 yum的使用1.3 yum的背景生态 2. 项目开发与集成开发环境3. vim编辑器3.1 vim编辑器的常见模式与模式切换3.3 vim编辑器的使用3.3.1 命令模式下的常见命令:3.3.2 vim…...
在idea中配置tomcat服务器,然后部署一个项日
1.下载tomcat Tomcat下载 点击右边的tomcat8 找到zip点击下载 下载完,解压到你想放置的路径下 2.配置环境变量 打开设置找到高级系统设置点击环境变量 点击新建,变量名输入:CATALINA_HOME,变量值就是Tomcat的安装路径&#x…...
C语言例:设 int a=11; 则表达式 a+=a-=a*a 的值
注:软件为VC6.0 代码如下: #include<stdio.h> int main(void) {int a11, b;b (aa-a*a); //a*a121 -->a-121结果为a-110 -->a-110结果为a-220printf("表达式aa-a*a 的值为: %d\n",b);return 0; } //优先级&#x…...
C++ 中的虚函数和多态性
C 是一种高级编程语言,它具有面向对象编程的特性。在 C 中,虚函数和多态性是非常重要的概念,它们使得继承关系更加灵活和强大。 虚函数是在基类中声明为虚函数的成员函数,其作用是在运行时动态绑定函数的调用。当在派生类中重写基…...
叶顺舟:手机SoC音频趋势洞察与端侧AI技术探讨 | 演讲嘉宾公布
后续将陆续揭秘更多演讲嘉宾! 请持续关注! 2024中国国际音频产业大会(GAS)将于2024年3.27 - 28日在上海张江科学会堂举办。大会将以“音无界,未来(Audio, Future)”为主题。大会由中国电子音响行业协会、上…...
SpringBoot之yml与properties配置文件格式的区别
概念: SpringBoot支持两种格式的配置文件,一种是yml,而另一种就是properties,默认的文件名为application.yml或者.properties 为什么有了properties之后还要有yml呢? 因为properties配置文件存在数据冗余性,在properties配置文件中一切配置都需要从头写到为, 并且Key不能重复,…...
【递归搜索回溯专栏】专题二:二叉树中的深搜----二叉树剪枝
本专栏内容为:递归,搜索与回溯算法专栏。 通过本专栏的深入学习,你可以了解并掌握算法。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:递归搜索回溯专栏 🚚代码仓库:小小unicorn的代…...
Django实现登录注册
Django实现登录注册 目录 Django实现登录注册配置路由首页注册前端:后端: 登录前端:后端:验证码部分逻辑 配置路由 首先分发路由[User,Blog,Article] from django.contrib import admin from django.urls import path from Blog…...
Python实战:NumPy数组与矩阵操作入门
NumPy是Python数据科学领域中不可或缺的库之一,它提供了一个强大的N维数组对象和一系列用于操作这些数组的函数。本文将详细介绍NumPy数组与矩阵的基础知识,包括数组的创建、操作、切片、索引、以及矩阵的运算等。 1. 引言 在Python数据科学领域&#…...
2024.2.26校招 实习 内推 面经
绿*泡*泡VX: neituijunsir 交流*裙 ,内推/实习/校招汇总表格 1、校招&实习 |美团2024年春季校园招聘全球启动(内推) 校招&实习 |美团2024年春季校园招聘全球启动(内推) 2、校招 | 江淮汽车2024…...
cannot find -xml2: No such file or directory的解决方法
一,问题现象 在编译库的时候出现如下图所示的报错:C:/msys64/mingw32/bin/…/lib/gcc/i686-w64-mingw32/13.2.0/…/…/…/…/i686-w64-mingw32/bin/ld.exe: ca nnot find -lxml2: No such file or directory collect2.exe: error: ld returned 1 exit s…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
