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

1. 深入理解ArrayList源码

1. ArrayList简介

ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。

ArrayList 继承了 AbstractList ,并实现了 List 接口。

主要特点

  1. 动态扩容:ArrayList的容量可以自动增长,不需要预先指定固定大小

  2. 随机访问:由于基于数组实现,可以通过索引快速访问元素(时间复杂度O(1))

  3. 非同步:ArrayList不是线程安全的

  4. 允许null元素:可以存储null值

  5. 有序集合:保持元素的插入顺序

基础用法:

// 创建ArrayList
ArrayList<String> list = new ArrayList<>();// 添加元素
list.add("Java");
list.add("Python");
list.add("C++");// 访问元素
String first = list.get(0);  // 获取第一个元素// 修改元素
list.set(1, "JavaScript");  // 修改第二个元素// 删除元素
list.remove(2);  // 删除第三个元素// 遍历
for (String language : list) {System.out.println(language);
}

与数组的比较

  1. 大小灵活性:数组大小固定,ArrayList大小可变

  2. 功能丰富性:ArrayList提供了更多内置方法(add, remove等)

  3. 性能:数组在内存使用和访问速度上略优


2. ArrayList源码分析

下面的一切都是基于jdk1.8 进行分析

2.1 ArrayList的成员变量和构造函数

        了解成员变量和构造函数是分析源码的第一步

成员变量:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {// 这些都是成员变量/*** 作用:序列化版本控制标识符* 说明:用于确保序列化和反序列化过程中类的版本兼容性*/private static final long serialVersionUID = 8683452581122892189L;/*** 作用:默认初始容量* 说明:当使用无参构造函数创建ArrayList且第一次添加元素时,默认创建的数组大小*/private static final int DEFAULT_CAPACITY = 10;/*** 作用:空数组共享实例* 说明:用于构造具有指定初始容量为0的ArrayList时使用*/private static final Object[] EMPTY_ELEMENTDATA = new Object[0];/*** 作用:默认容量空数组共享实例* 说明:用于无参构造函数创建的 ArrayList,与 EMPTY_ELEMENTDATA 区分是为了知道第一次添加元素时需要扩容多少*/private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];/*** 作用:实际存储元素的数组缓冲区* 说明:*      transient修饰表示序列化时不会自动序列化此字段*      ArrayList通过自定义的 writeObject 和 readObject 方法来控制序列化过程*      实际类型是 Object[],但通过泛型在运行时进行类型检查*/transient Object[] elementData;/*** 作用:ArrayList中实际元素的数量* 说明:*      不等于elementData数组的长度(容量)*      表示列表中实际包含的元素个数*      size <= elementData.length*/private int size;/*** 作用:数组最大容量限制* 说明:*      值为2147483639 (Integer.MAX_VALUE - 8)*      减 8 是因为某些虚拟机在数组中保留 header words*      尝试分配更大的数组可能导致 OutOfMemoryError*/private static final int MAX_ARRAY_SIZE = 2147483639;
}

构造函数: 

        // 带初始容量的构造参数public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else {if (initialCapacity != 0) {throw new IllegalArgumentException("Illegal Capacity: " +                                             initialCapacity);}this.elementData = EMPTY_ELEMENTDATA;}}// 无参构造函数public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}// Collection 是集合的父接口// 将collection对象转换成数组,然后将数组的地址的赋给elementDatapublic ArrayList(Collection<? extends E> c) {Object[] a = c.toArray();if ((size = a.length) != 0) {if (c.getClass() == ArrayList.class) {elementData = a;} else {elementData = Arrays.copyOf(a, size, Object[].class);}} else {elementData = EMPTY_ELEMENTDATA;}}

 2.2 ArrayList的添加扩容(第一次添加)

基本扩容流程

  1. 初始容量:默认初始容量为10(在第一次添加元素时真正分配)

  2. 扩容触发条件:当尝试添加元素时(add操作),发现当前元素数量等于数组容量

  3. 扩容大小:新容量 = 旧容量 + (旧容量 >> 1)(即大约1.5倍)

关键源码分析

// 添加元素方法
public boolean add(E e) {ensureCapacityInternal(size + 1);  // 确保容量足够elementData[size++] = e;return true;
}private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}private static int calculateCapacity(Object[] elementData, int minCapacity) {return elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(DEFAULT_CAPACITY, minCapacity) : minCapacity;
}private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果所需最小容量大于当前数组长度,则扩容if (minCapacity - elementData.length > 0)grow(minCapacity);
}// 核心扩容方法
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量=旧容量*1.5if (newCapacity - minCapacity < 0) // 特殊情况处理newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0) // 处理超大容量newCapacity = hugeCapacity(minCapacity);// 复制数据到新数组elementData = Arrays.copyOf(elementData, newCapacity);
}

接下来我们一步一步分析,先看代码:

先创建 ArrayList 并添加数据:

ArrayList<Integer> list = new ArrayList<>(); // 初始空数组
list.add(1);  // 第一次添加,扩容到10

 初始化空数组时会调用无参构造,创建一个空的 ArrayList

// 无参构造函数
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

当我们使用 add 方法添加数据时,方法内部就是执行添加、扩容操作。

具体流程如下:

  • add 方法(添加元素):
// 添加元素方法
public boolean add(E e) {ensureCapacityInternal(size + 1);  // 确保容量足够elementData[size++] = e;return true;
}

   先确保内部容量足够所以调用 ensureCapacityInternal 方法验证,

size 是 实际元素数量。

  • ensureCapacityInternal 方法(确保内部容量):
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

在这个方法内部还有一个方法 calculateCapacity(elementData, minCapacity),

calculateCapacity 方法是用来计算容量

参数:

elementData :实际存储元素的数组缓冲区。

        这个 elementData 其实就是成员变量当中的 transient Object[] elementData;

        它是通过无参构造附的值 实际上 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  •  calculateCapacity 方法(计算容量):
private static int calculateCapacity(Object[] elementData, int minCapacity) {return elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(DEFAULT_CAPACITY, minCapacity) : minCapacity;
}// 不同版本可能略有差异,有的是用 if 语句判断
private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;
}

DEFAULTCAPACITY_EMPTY_ELEMENTDATA :默认容量空数组共享实例  。

DEFAULT_CAPACITY :默认初始容量。

通过calculateCapacity 方法(计算容量)拿到返回值,

由于是第一次进行扩容,其实拿到的返回值就是 10。

然后回到 ensureCapacityInternal 方法(确保内部容量),此时的方法应为:

private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(10);
}

然后继续调用 ensureExplicitCapacity 方法()。

  • ensureExplicitCapacity 方法(确保显式容量):
private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果所需最小容量大于当前数组长度,则扩容if (minCapacity - elementData.length > 0)grow(minCapacity);
}

 这个方法里面设计一个判断,如果所需最小容量大于当前数组长度(相减大于 0),则扩容

扩容方法就是 grow 方法。

  • grow 方法(扩容):
// 核心扩容方法
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量=旧容量*1.5if (newCapacity - minCapacity < 0) // 特殊情况处理newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0) // 处理超大容量newCapacity = hugeCapacity(minCapacity);// 复制数据到新数组elementData = Arrays.copyOf(elementData, newCapacity);
}

 这个方法中,主要进行以下操作:

  • 第一行代码先去拿到了数组的容量,然后赋值给 oldCapacity(旧容量)。
  • 第二行就是扩容操作,>>1 这是位运算,向右移动 1 位,也就是 除以 2 ,再加上原来的长度(容量),所以 新容量=旧容量*1.5 。
  • 最后一行也就是数组拷贝,复制数据到新数组,新数组的长度也就是 newCapacity(新容量)。
  • 新数组与原数组地址不同,但数组内储存的数据是相同的

数组拷贝完成之后,就会回到最开始的 add 方法(添加元素),继续执行

 继续执行add 方法(添加元素):

// 添加元素方法
public boolean add(E e) {ensureCapacityInternal(size + 1);  // 确保容量足够elementData[size++] = e;return true;
}

执行 第二行 elementData[size++] = e;

为 数组的 size 下标处进行赋值,值为 e,也就是咱们最开始传进来的 “1”。

ArrayList<Integer> list = new ArrayList<>(); // 初始空数组
list.add("1");  // 第一次添加,扩容到10

赋完值后,执行 size++,

注意:

// 复制数据到新数组
elementData = Arrays.copyOf(elementData, newCapacity);

  1. Arrays.copyOf() 会创建新数组,所以 elementData 不再指向 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  2. 元素是引用复制(新数组和旧数组的元素指向相同的对象),但 数组对象本身是新创建的(地址不同)。

  3. 扩容不会影响原有元素的对象,只是重新分配了一个更大的数组并复制引用。

以上就是第一次扩容的全过程。


2.3 ArrayList的添加扩容(第2 - 10 次添加)

第 2——10 次添加,是不会走扩容操作的,因为在第一次扩容后,数组长度就是 10,容量一直是足够。

我们还是继续分析一波:

我们继续进行第2——10次添加:

ArrayList<Integer> list = new ArrayList<>(); // 初始空数组
list.add(1);  // 第一次添加,扩容到10
for(int i = 2; i < 10; i++) {list.add(i);
}

 第2——9次肯定是没有问题的,咱们主要说第 10 次添加。

第 10 次添加:

  • add 方法:
// 添加元素方法
public boolean add(E e) {ensureCapacityInternal(size + 1);  // 确保容量足够elementData[size++] = e;return true;
}

当进行第 10 次添加,此时 size 的值应该为 9,因为添加了九次。

size + 1 = 10 ,所以 10 继续进入方法 ensureCapacityInternal 执行。

  • ensureCapacityInternal 方法,及其内部方法:
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}private static int calculateCapacity(Object[] elementData, int minCapacity) {return elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(DEFAULT_CAPACITY, minCapacity) : minCapacity;
}private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果所需最小容量大于当前数组长度,则扩容if (minCapacity - elementData.length > 0)grow(minCapacity);
}

进入 ensureCapacityInternal 方法中,继续调用 calculateCapacity 方法。

此时传入 calculateCapacity 的参数中 minCapacity = 10 。

calculateCapacity 中:

由于经过了第一次扩容,条件elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA不在成立,

所以直接返回minCapacity,也是 10。

然后继续 ensureExplicitCapacity(10) :

  • minCapacity = 10
  • elementData.length = 10(当前数组长度)
  • minCapacity - elementData.length > 0,条件不成立,所以不会进行扩容。

所以,第2 - 10 次添加都不会进行扩容。


2.4 ArrayList的添加扩容(第 11 次添加)

第 11 次添加,这个超出数组长度肯定要进行扩容。

我们还是一步一步分析~~~

我们继续进行第 11 次添加:

ArrayList<Integer> list = new ArrayList<>(); // 初始空数组
list.add(1);  // 第一次添加,扩容到10
for(int i = 2; i < 10; i++) {list.add(i);
}
list.add(11); 

第 11 次添加:

  • add 方法:
// 添加元素方法
public boolean add(E e) {ensureCapacityInternal(size + 1);  // 确保容量足够elementData[size++] = e;return true;
}
  • 此时,size = 10 ,size + 1 = 11
  • 11 代入 ensureCapacityInternal 方法。
  • ensureCapacityInternal 方法,及其内部方法:
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}private static int calculateCapacity(Object[] elementData, int minCapacity) {return elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(DEFAULT_CAPACITY, minCapacity) : minCapacity;
}private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果所需最小容量大于当前数组长度,则扩容if (minCapacity - elementData.length > 0)grow(minCapacity);
}

先执行 calculateCapacity 方法:

  • minCapacity = 11
  • 由于经过了数组拷贝,条件elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA不在成立
  • 直接返回 minCapacity

再执行 ensureExplicitCapacity 方法:

  • minCapacity = 11
  • elementData.length = 10
  • 所以 minCapacity - elementData.length > 0 ,条件成立
  • 执行 grow 方法进行扩容
  • grow 方法(扩容):
// 核心扩容方法
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量=旧容量*1.5if (newCapacity - minCapacity < 0) // 特殊情况处理newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0) // 处理超大容量newCapacity = hugeCapacity(minCapacity);// 复制数据到新数组elementData = Arrays.copyOf(elementData, newCapacity);
}
  •  minCapacity = 11
  • 第一行 oldCapacity = 10 (原来数组长度)
  • 第二行 newCapacity = 10 + 5 = 15 (新数组长度)
  • 进行 Arrays.copyOf 拷贝,新数组和原来数组的地址是不一致的。

拷贝完成之后,继续回到 add 方法为数组的第 11 号位进行赋值。

这就是后续扩容的步骤。


3. 问题总结 

3.1 ArrayList的底层原理实现?

  • ArrayList底层是用动态的数组实现的
  • ArrayList初始容量为0,当第一次添加数据的时候才会初始化容量为10
  • ArrayList在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数组
  • ArrayList在添加数据的时候进行的步骤:
    1. 确保数组已使用长度(size)加1之后足够存下下一个数据
    2. 计算数组的容量,如果当前数组已使用长度+1后的大于当前的数组长度,则调用grow方法扩容(原来的1.5倍)
    3. 确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。
    4. 返回添加成功布尔值。

3.2 如果在创建ArrayList时声明数组长度,list集合会扩容几次? 

eg:ArrayList list=new ArrayList(10)

指定初始大小为 10,会进行下面的构造函数进行创建:

 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);}
}

关键逻辑解析:

  1. initialCapacity > 0

    • 直接创建一个大小为 initialCapacity 的 Object[] 数组,赋值给 elementData

    • 示例new ArrayList(10) → elementData = new Object[10](但此时 size=0,因为还没添加元素)。

  2. initialCapacity == 0

    • 使用 EMPTY_ELEMENTDATA(共享的空数组),避免不必要的内存分配。

    • 示例new ArrayList(0) → elementData = EMPTY_ELEMENTDATA

  3. initialCapacity < 0

    • 抛出 IllegalArgumentException 异常。

    • 示例new ArrayList(-1) → 直接报错。


很显然“直接创建一个大小为 initialCapacity 的 Object[] 数组,赋值给 elementData”,

所以并不会进行扩容,因此答案是 0 次。


http://www.dtcms.com/a/279446.html

相关文章:

  • ae如何安装在非C盘
  • 7.15 窗口函数 | 二分 | 位运算
  • 逻辑代数中的基本规则,代入规则和反演规则,对偶规则
  • LLM notes
  • GitCode 使用高频问题及解决方案
  • TextIn:大学生的文档全能助手,让学习效率飙升
  • 【Linux庖丁解牛】— 信号的产生!
  • SwiftUI 常用控件分类与使用指南
  • SCI特刊征稿
  • 延迟双删懂不
  • .net swagger的API项目里面 同时可以运行wwwroot里面的网页
  • Java 中的异步编程详解
  • Desktop Extensions (DXT) 详解
  • CA翻译~
  • 12.如何判断字符串是否为空?
  • 153、寻找旋转排序数组中的最小值
  • 本地线程(Native Thread)、主线程(UI 线程) 和 子线程(Java 子线程)
  • Axure RP Extension for Chrome插件安装使用
  • 在 Ubuntu 上安装 vLLM:从 GPU 到 CPU 的三种方案
  • Oracle根据一张表的字段更新另一张表中的数据
  • Android 自定义路由系统
  • ServiceLibrary 库使用演示
  • [AI8051U入门第一步]环境安装和按键控制流水灯
  • 将dist文件打包成exe可执行程序
  • MySQL服务故障分析报告​​
  • 以楼宇自控系统为抓手,实现人居环境优化与建筑能效跃升
  • 职业教育领域的“101计划
  • keepalive模拟操作部署
  • 学习日志09 python
  • 【SVN】SVN 客户端的安装与操作