ArrayList中add()方法的执行过程

本文详细解析了Java中ArrayList的add()方法执行流程,包括如何计算最小容量、判断是否需要扩容及扩容策略,深入探讨了ArrayList的底层实现机制。

ArrayList中add()方法的执行过程

步骤概述

ArrayList底层实现本质上是在对一个数组进行操作。

在对某个ArrayList实例对象执行add(E e)方法时,新增的元素将被追加到该实例的末尾,其内部的执行逻辑大致如下:

  1. 调用add(E e)方法

  2. 获取当前ArrayList实例的元素个数:size

  3. 计算新元素追加完成后的实例应该具备的最小容量:minCapacity = size + 1

  4. 获取当前ArrayList实例elementData

    • 如果该实例为new时创建的默认对象,获取一个默认的初始容量DEFAULT_CAPACITY=10
    • 如果该实例非新构建的对象,则获取该实例追加元素后应该具备的最小容量minCapacity
  5. 元素追加 或 进行扩容+数据迁移操作

    • 追加元素后应该具备的最小容量minCapacity < 当前集合容量elementData.length执行数据追加操作,直接跳到第8步

    • 集合刚被创建时minCapacity > elementData.length执行扩容+数据迁移操作

    • 追加元素时如果minCapacity > elementData.length执行扩容操作+数组迁移操作

  6. 执行扩容操作,返回扩容后容量

    • 先将容量扩容到当前容量的1.5倍,判断是否够用:minCapacity < newCapacity

      • 不够用将容量扩容为minCapacity
    • 判断扩容后的容量是否超出了数组限定的最大容量MAX_ARRAY_SIZE= Integer.MAX_VALUE - 8

      为什么数组的最大值是Integer.MAX_VALUE - 8?

      数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。

      如果数组长度过大,可能出现的两种错误

      OutOfMemoryError: Java heap space 堆区内存不足(这个可以通过设置JVM参数 -Xmx 来指定)。

      OutOfMemoryError: Requested array size exceeds VM limit 超过了JVM虚拟机的最大限制

      • 扩容后容量未超出MAX_ARRAY_SIZE,返回扩容后容量newCapacity
      • 扩容后容量超出MAX_ARRAY_SIZE,返回Integer.MAX_VALUE
  7. 执行Arrays.copyOf(elementData, newCapacity)操作,复制当前实例对象到新的数组

  8. 执行新元素的追加操作elementData[size++] = e,size在方法内自增

源码

/**
 * 将指定的元素追加到此列表的末尾。
 */
public boolean add(E e) {
	//扩容运算,原数据迁移
	ensureCapacityInternal(size + 1);
	
	//追加新元素,size自增
	elementData[size++] = e;
	
	return true;
}


..........此处省略无关代码..........

    
/**
 * 得到一个初始容量或新元素添加完成后所需的最小容量
 * @param elementData 当前实例对象
 * @param minCapacity 所需的最小容量 = size + 1
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	// DEFAULTCAPACITY_EMPTY_ELEMENTDATA是使用空参构造的默认空数组
	// elementData如果与其相等,则当前实例创建后未进行过任何操作,此时返回默认容量10
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	
	return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(
		calculateCapacity(elementData, minCapacity)
	);
}

private void ensureExplicitCapacity(int minCapacity) {
	modCount++;
	
	if (minCapacity - elementData.length > 0)
		// 容量溢出,进行扩容运算和原数据迁移
		grow(minCapacity);
}

/**
 * 要分配的数组的最大大小。
 * 一些VM在数组中保留一些标题字
 * 尝试分配更大的数组可能会导致OutOfMemoryError:请求的数组大小超出了VM限制
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * 增加容量以确保它至少可以容纳最小容量参数指定的元素数。
 *
 * @param minCapacity 所需的最小容量
 */
private void grow(int minCapacity) {
	// 先将当前实例的数组容量扩容到1.5倍
	int oldCapacity = elementData.length;				//当前容量
	int newCapacity = oldCapacity + (oldCapacity >> 1); //位移运算 >> 1 相当于除以2
	
	// 扩容后不够用,将数组容量直接扩容到实际长度
	if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;
	
	//还是不够用,扩容Integer.MAX_VALUE=2147483647
	if (newCapacity - MAX_ARRAY_SIZE > 0)
		newCapacity = hugeCapacity(minCapacity);
	
	//扩容完成,复制当前实例数据到新的数组
	elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
	if (minCapacity < 0) // overflow
		throw new OutOfMemoryError();
	return (minCapacity > MAX_ARRAY_SIZE) ?
		Integer.MAX_VALUE :
		MAX_ARRAY_SIZE;
}

<think>我们正在讨论ArrayListadd方法执行过程。根据提供的引用信息,我们可以总结如下: 1. 当调用add(E e)方法时,首先会进行扩容判断(ensureCapacityInternal(size + 1))[^1]。 2. 扩容判断中,如果ArrayList是通过无参构造创建的,那么初始数组长度为10[^2]。 3. 在扩容过程中,会计算最小容量minCapacity(即当前元素个数+1),然后与当前数组长度比较,如果不够则扩容[^3]。 具体执行过程如下: - 步骤1:检查当前数组容量是否足够添加新元素。 - 通过`ensureCapacityInternal(size + 1)`确保容量足够。其中`size`是当前列表中元素的个数。 - 在`ensureCapacityInternal`方法内部: - 首先判断当前数组是否是默认的空数组(如果是通过无参构造创建且未添加过元素,则elementData是一个空数组,初始长度为0,但第一次添加时会扩展到默认容量10)[^2][^3]。 - 计算所需的最小容量`minCapacity`(即`size+1`)。 - 如果当前数组是空数组(即初始状态),则所需的最小容量取默认容量(10)和`minCapacity`的最大值。 - 然后调用`ensureExplicitCapacity`方法,该方法中如果`minCapacity`大于当前数组长度,则进行扩容(`grow`方法)。 - 步骤2:扩容操作(如果需要)。 - 扩容时,新容量通常是旧容量的1.5倍(通过`int newCapacity = oldCapacity + (oldCapacity >> 1);`计算)[^1][^3]。 - 如果计算出的新容量小于所需的最小容量,则使用最小容量。 - 如果新容量超过数组最大容量(`MAX_ARRAY_SIZE`),则处理为大容量情况(使用`hugeCapacity`方法)。 - 最后,使用`Arrays.copyOf`将原数组复制到新数组中。 - 步骤3:将新元素添加到数组的指定位置。 - 执行`elementData[size] = e;`,将新元素放入数组的size索引处。 - 然后`size++`,即元素个数加1。 - 步骤4:返回true(表示添加成功)。 另外,对于add(int index, E element)方法(在指定位置插入元素),过程类似,但会先检查索引是否越界,然后同样进行扩容判断,之后需要将插入位置后的元素向后移动一位(使用`System.arraycopy`),最后在指定位置放入元素。 总结一下add(E e)方法执行过程: 1. 扩容检查(确保可以添加一个新元素)。 2. 如果需要扩容,则进行扩容(通常扩容为原来的1.5倍,但第一次添加时扩容到10)。 3. 将新元素添加到数组末尾(size位置)。 4. 大小增加1。 下面我们用代码步骤来描述: ```java public boolean add(E e) { // 1. 确保内部容量足够,参数为当前size+1(即添加后需要的最小容量) ensureCapacityInternal(size + 1); // 这一步可能触发扩容 // 2. 在数组的size位置赋值,然后将size加1 elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { // 如果当前数组是默认的空数组(即初始状态),那么最小容量取默认容量(10)和minCapacity的最大值 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(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.5 if (newCapacity - minCapacity < 0) // 如果计算的新容量小于所需最小容量,则使用最小容量 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) // 如果新容量超过最大容量,则调整为最大容量或Integer.MAX_VALUE newCapacity = hugeCapacity(minCapacity); // 将原数组复制到新容量的数组中 elementData = Arrays.copyOf(elementData, newCapacity); } ``` 以上就是ArrayListadd方法执行过程。 §§相关问题§§ 1. ArrayList在扩容时为什么选择1.5倍的扩容因子? 2. 如何避免ArrayList频繁扩容带来的性能开销? 3. ArrayListadd(int index, E element)方法是如何实现的?与add(E e)有何不同? </think>### ArrayListadd方法执行过程 ArrayList的`add(E e)`方法执行过程如下(基于Java源码逻辑): 1. **容量检查与扩容** 调用`ensureCapacityInternal(size + 1)`[^1][^3]: - 若ArrayList通过无参构造创建(初始为空数组),首次添加时设置最小容量为默认值10[^2]。 - 计算最小容量`minCapacity = size + 1`(size为当前元素数量)。 - 若`minCapacity > 当前数组长度`,触发扩容: - 新容量 = 旧容量 × 1.5(通过`oldCapacity + (oldCapacity >> 1)`计算) - 创建新数组并复制原数据(`Arrays.copyOf`) 2. **元素添加** ```java elementData[size++] = e; // 将元素存入数组末尾 ``` - 在数组的`size`位置插入新元素 - `size`自增1(记录元素总数) 3. **返回结果** `return true;` 表示添加成功 #### 示例流程 假设首次添加元素: 1. 无参构造创建ArrayList → 初始数组长度为10[^2] 2. 调用`add("A")`: - `ensureCapacityInternal(1)`检查容量(1 < 10,不扩容) - `elementData[0] = "A"; size = 1` 3. 添加第11个元素: - `minCapacity = 11 > 10` → 触发扩容 - 新容量 = 10 × 1.5 = 15 - 复制原数组到新数组后添加元素 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值