ArrayList 本质是通过一个可变长的数组来存储不定量的元素,且当元素通过不同方式被添加存储时,总是先计算自身容量,若符合扩容标准则对数组进行扩容并拷贝原有元素

本文将基于 JDK8 对 ArrayList 源码中的构造ArrayList()、存储add()、删除remove()、扩容grow()、序列化(writeObject()readObject()) 等过程中所涉及 JDK 源码做行级解释

若您有遇到其它相关问题,非常欢迎在评论中留言,我和其他读者小伙伴们将帮助解决并持续更新至此文,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!


ArrayList 的结构定义

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {// 略···
}

定义 ArrayList 时继承 AbstractList 抽象类表示这是一个数组队列,含有增删改查遍历等常用操作。实现 RandomAccess 接口表示此对象可进行随机访问。实现 Cloneable 接口表示此对象可被克隆。实现 Serializable 接口表示此对象可被序列化与反序列化,接下来开始进入正题!


ArrayList 实例化

ArrstList 共提供了 3 种 构造方法,支持指定长度和指定元素内容,满足各种常见场景下对容量的需求

    // 默认初始容量private static final int DEFAULT_CAPACITY = 10;// 若初始化时指定了长度或添加集合,但长度为0,则赋值为此对象。若elementData等于此对象,表示有参实例化,首次add时数组仅扩容至1// 如:ArrayList<Integer> mArrayList = new ArrayList<Integer>(0);private static final Object[] EMPTY_ELEMENTDATA = new Object[0];// 若初始化时不指定长度或添加集合则赋值为此对象。若elementData等于此对象,表示是无参实例化,首次add时数组默认扩充容量至10// 如:ArrayList<Integer> mArrayList = new ArrayList<Integer>(); // 区别赋值的原因猜测是因为后者实例方式更符合实际开发需要,所以首次就给了较多容量,减少多次扩容的资源开支private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];// elementData 是用来保存元素的数组。因为他的容量通常大于实际使用量,会产生空余空间,所以被修饰为transient ,// 表示不可被序列化,避免序列化时时间与空间的浪费,而 ArrayList 的序列化与反序列化通过writeObject和readObject完成transient Object[] elementData;// 当前数组长度private int size;// 最大数组长度private static final int MAX_ARRAY_SIZE = 2147483639;// 第1种:初始化一个长度为0的空数组public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}// 第2种:初始化时指定数组长度public ArrayList(int var1) {// 检查长度参数 var1 是否合法,若合法则按需创建数组if (var1 > 0) {this.elementData = new Object[var1];} else {// 若 var1 非零则表示为负数,抛出非法参数异常if (var1 != 0) {throw new IllegalArgumentException("Illegal Capacity: " + var1);}// 若 var1 为零,则创建长度为0的数组this.elementData = EMPTY_ELEMENTDATA;}}// 第3种:初始化时批量添加元素public ArrayList(Collection<? extends E> var1) {// 将传入的集合转为数组,并将结果进行赋值this.elementData = var1.toArray();// 若转换结果不为空,则继续处理if ((this.size = this.elementData.length) != 0) {if (this.elementData.getClass() != Object[].class) {this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);}} else {// 若传入的集合为空,则仅仅只是初始化一个空数组this.elementData = EMPTY_ELEMENTDATA;}}

ArrayList 添加元素

ArrstList 共提供 4 种添加元素方法,支持指定位置、批量添加,在此检查是否符合扩容条件

    // 第1种:添加一个在数组创建时指定类型的元素public boolean add(E var1) {// 检查是否需要扩容this.ensureCapacityInternal(this.size + 1);// 向数组尾部插入新元素(size表示当前数组中最后一个被插入元素的下标)this.elementData[this.size++] = var1;return true;}// 第2种:在指定位置,添加一个在数组创建时指定类型的元素public void add(int var1, E var2) {// 检查下标边界是否合法this.rangeCheckForAdd(var1);// 检查是否需要扩容this.ensureCapacityInternal(this.size + 1);// 移动当前数组内元素位置System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1);// 向指定位置插入新元素this.elementData[var1] = var2;// 更新尾元素下标位置++this.size;}// 第3种:添加一组指定类型的元素public boolean addAll(Collection<? extends E> var1) {// 转化为数组Object[] var2 = var1.toArray();// 记录数组长度int var3 = var2.length;// 检查是否需要扩容this.ensureCapacityInternal(this.size + var3);// 向数组尾部插入一组新元素System.arraycopy(var2, 0, this.elementData, this.size, var3);// 更新尾元素下标位置this.size += var3;// 返回批量添加结果return var3 != 0;}// 第4种:指定起始位置,添加一组指定类型的元素,public boolean addAll(int var1, Collection<? extends E> var2) {// 检测起始位置是否合法this.rangeCheckForAdd(var1);// 转化为数组Object[] var3 = var2.toArray();// 记录数组长度int var4 = var3.length;// 检查是否需要扩容this.ensureCapacityInternal(this.size + var4);// 计算所需插入区间的下标int var5 = this.size - var1;if (var5 > 0) {// 移动当前元素位置System.arraycopy(this.elementData, var1, this.elementData, var1 + var4, var5);}// 将新数组插入指定位置System.arraycopy(var3, 0, this.elementData, var1, var4);// 更新尾元素下标位置this.size += var4;// 返回批量添加结果return var4 != 0;}

相关方法源码解析

    // 计算数组长度private void ensureCapacityInternal(int var1) {// 若当前数组长度为 0 ,则比较 (10) 与 (目标长度) ,结果取大并更新目标数组长度值if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {var1 = Math.max(10, var1);}// 根据目标数组长度值,检查是否需要扩容this.ensureExplicitCapacity(var1);}// 检查是否需要扩容private void ensureExplicitCapacity(int var1) {// fail-fast 机制用到的修改次数计数(ArrayList非线程安全,所以在使用迭代器过程中被修改的话,会抛出 ConcurrentModificationExceptions)++this.modCount;// 如果计算出来的目标数组长度 大于 当前数组的长度,则表示数组需要扩容if (var1 - this.elementData.length > 0) {this.grow(var1);}}// 数组扩容private void grow(int var1) {// 当前数组长度int var2 = this.elementData.length;// 扩容数组长度(默认1.5倍,也就是增加当前长度的50%)int var3 = var2 + (var2 >> 1);// 如果扩容数组长度 小于 传入的目标数组长度,则更新扩容目标if (var3 - var1 < 0) {var3 = var1;}// 扩容最大边界检查if (var3 - 2147483639 > 0) {var3 = hugeCapacity(var1);}// 数组扩容至目标长度,且保存现有元素this.elementData = Arrays.copyOf(this.elementData, var3);}// 检查目标插入下标边界是否合法private void rangeCheckForAdd(int var1) {if (var1 > this.size || var1 < 0) {throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));}}// 扩容长度检查private static int hugeCapacity(int var0) {if (var0 < 0) {// 非法参数则抛出异常throw new OutOfMemoryError();} else {// 如果扩容目标大于 最大允许数组长度,则比较 是否大于 2147483639,大于的话 扩容目标=2147483647,反之扩容目标=2147483639return var0 > 2147483639 ? 2147483647 : 2147483639;}}

ArrayList 删除元素

ArrstList 共提供 3 种删除元素方法,支持通过下标或对象删除

    // 第1种:删除指定下标的元素(从0开始)public E remove(int var1) {// 检测下标是否合法this.rangeCheck(var1);// fail-fast 机制用到的修改次数计数(ArrayList非线程安全,所以在使用迭代器过程中被修改的话,会抛出 ConcurrentModificationExceptions)++this.modCount;// 获取即将被删除的元素Object var2 = this.elementData(var1);// 计算需要移动的元素数量int var3 = this.size - var1 - 1;if (var3 > 0) {// 移动元素(被删除的元素,将被它后一个元素直接覆盖)System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var3);}// 将尾部元素置空,因为它已被复制到前一个下标位置this.elementData[--this.size] = null;// 返回处理结果return var2;}// 第2种:删除指定对象public boolean remove(Object var1) {// 遍历起始下标int var2;// 如果需要删除空对象if (var1 == null) {// 遍历当前数组中的空对象(但仅仅删除首个符合要求的元素)for(var2 = 0; var2 < this.size; ++var2) {// 发现目标if (this.elementData[var2] == null) {// 删除this.fastRemove(var2);return true;}}} else {// 遍历数组中的目标对象(但仅仅删除首个符合要求的元素)for(var2 = 0; var2 < this.size; ++var2) {// Object 的 equals 和 == 比较的均是地址值// String 的 == 比较的是值,equals 则先比较地址,再顺序比较字符if (var1.equals(this.elementData[var2])) {// 若相等则删除this.fastRemove(var2);return true;}}}return false;}// 第3种:清空public void clear() {++this.modCount;for(int var1 = 0; var1 < this.size; ++var1) {this.elementData[var1] = null;}this.size = 0;}

相关方法源码解析

    // 检查下标是否合法private void rangeCheck(int var1) {// 若删除的元素下标大于数组内最后一个元素的下标,抛出 IndexOutOfBoundsException// var1 是下标,从0开始 ; this.size 是数组长度,从1开始,所以最后一个元素的下标最大也只能是 this.size - 1if (var1 >= this.size) {throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));}}// 执行 remove() 的删除操作(也可以讲是覆盖)private void fastRemove(int var1) {// 执行了删除操作,计数更新(用于 Fail-Fast 机制)++this.modCount;// 计算需要移动的元素数量int var2 = this.size - var1 - 1;if (var2 > 0) {// 移动元素(被删除的元素,将被它后一个元素直接覆盖)System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var2);}// 将尾部元素置空,因为它已被复制到前一个下标位置this.elementData[--this.size] = null;}

System.arraycopy() 数组复制

在对 ArrayList 进行操作的的过程中,System.arraycopy() 这个 native 方法被频繁使用,它的作用是实现高效的数组间复制,在此标注下各项参数含义,避免产生困扰

public static native void arraycopy(Object var0, int var1, Object var2, int var3, int var4);

Object var0 :源数组
int var1 : 源数组复制起始下标
Object var2 : 目标数组
int var3 : 目标数组存储起始下标
int var4 : 需复制长度


ArrayList 序列化

通过源码我们可以看到这项定义: transient Object[] elementData;,这表示被 transient 修饰的数组不可被序列化,但 elementData 又是实际存储了元素的数组,且 ArrayList 肯定可以序列化传输,那么这么操作的原因是为什么?

很简单,当 Arraylist 被定以后,每次添加元素都需检查当前容量是否符合扩容标准,而扩容并不会在数组长度被用完时进行,所以数组的长度总是大于实际使用长度,而在这种情况下直接序列化对象,必然造成时间成本与空间资源的浪费,所以 ArrayList 内提供了 writeObjectreadObject 两个方法,以 this.size 为循环条件,只处理有效元素

 transient Object[] elementData;// 略···private void writeObject(ObjectOutputStream var1) throws IOException {int var2 = this.modCount;var1.defaultWriteObject();var1.writeInt(this.size);for(int var3 = 0; var3 < this.size; ++var3) {var1.writeObject(this.elementData[var3]);}if (this.modCount != var2) {throw new ConcurrentModificationException();}}private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {this.elementData = EMPTY_ELEMENTDATA;var1.defaultReadObject();var1.readInt();if (this.size > 0) {this.ensureCapacityInternal(this.size);Object[] var2 = this.elementData;for(int var3 = 0; var3 < this.size; ++var3) {var2[var3] = var1.readObject();}}}

若您有遇到其它相关问题,非常欢迎在评论中留言,我和其他读者小伙伴们将帮助解决并持续更新至此文,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!

深深的码丨Java ArrayList 源码透析相关推荐

  1. 深深的码丨Java HashMap 源码透析

    Hashmap 的数据结构基础是基于一维数组实现的,向其添加元素时通过计算key的hash值来确定具体存储位置.添加元素过程中若出现hash冲突,也就是N个元素key的hash值相等,处理方式为:将元 ...

  2. 【Java源码分析】ArrayList源码分析

    类的定义 public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAc ...

  3. 【源码解析】ArrayList源码解析

    存储特性 – 有序且可重复 存储元素,底层 Object 数据,数组不会对元素做判断,所以可重复,基于数组下标的连续存储,所以有序 数组容量一旦定义,就不能更改,可以扩容 初始容量 – 默认情况下,a ...

  4. Redis源码和java jdk源码中hashcode的不同实现

    一.redis实际上是使用了siphash 这个比较简单,我说的简单是指redis代码比较少不像jdk一样调用C++代码调用栈非常深. 先看这个rehashing.c 主要就是dictKeyHash函 ...

  5. java ArrayList源码分析(转载)

    1.ArrayList是一个相对来说比较简单的数据结构,最重要的一点就是它的自动扩容,可以认为就是我们常说的"动态数组". 来看一段简单的代码: 12345 ArrayList&l ...

  6. java桌面通讯录源码_TONGXUNLU JAVA通讯录源码 JAVA课程设计源码 讯友桌面通讯录 通讯录管理 - 下载 - 搜珍网...

    讯友桌面通讯录/.classpath 讯友桌面通讯录/.project 讯友桌面通讯录/.settings/org.eclipse.jdt.core.prefs 讯友桌面通讯录/bin/com/zzk ...

  7. Java集合源码分析(二)ArrayList

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

  8. 【源码阅读】Java集合之一 - ArrayList源码深度解读

    Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础: 针对Collection的源码阅读写一个系列的文章,从ArrayList开始第一篇. ---@pdai JDK版本 ...

  9. ArrayList源码详细解析(一)

    Java ArrayList源码解析(基于JDK 12,对比JDK 8) 自从接触到ArrayList以来,一直觉得很方便,但是从来没有系统.全面的学习了解过ArraryList的实现原理.最近学习了 ...

最新文章

  1. 最大子序列求和_最大子序列和问题
  2. mt7628 pcie挂载nvme并测试
  3. linux shmmax单位,Linux核心参数Shmmax,shmall,shmni
  4. http服务器返回状态代码含义
  5. Sendmail邮件服务器搭建与配置笔记
  6. 【算法】给定一个链表,判断链表中是否有环
  7. 2019年1024,deepin安装原生Linux QQ
  8. matlab中的封装引脚,lm5117封装引脚图及功能
  9. 信息收集----谷歌语句
  10. Python网络爬虫反爬破解策略实战
  11. 湖南大学ACM程序设计新生杯大赛(同步赛)L-Liao Han【打表规律+二分】
  12. 央企建筑公司数字化转型最大挑战是什么?如何破解?
  13. 邮件群发技巧,edm邮件5个技巧
  14. 袭扰战术_战术十必不可少的八
  15. 人生节点 | 2021年终总结2022年度计划
  16. 豆酱小白与python(一):提取B站弹幕并制作词云
  17. Go语言:爱吃香蕉的珂珂
  18. js中根据元素名获取对象,根据id获取等等。。。
  19. C语言学习22.9.4
  20. 一位架构师用服务打动客户的故事之二

热门文章

  1. RK3588人工智能主板ITX-3588J
  2. DL/T645-2007通信协议指令学习记录
  3. 全球存储观察:联想到底在想什么?
  4. 搜狗输入法五笔三个快捷键
  5. 计算机研究生博士课程设置,计算机科学与技术学科博士研究生培养方案.docx
  6. EMC 存储(步步高项目)
  7. 2023长安大学计算机考研信息汇总
  8. 蓝桥_Java实用总结
  9. cutterman的安装
  10. 打死都不要进外包,看看我在阿里外包的2年...