ArrayList讲解

ArrayList讲解

一 、ArrayList是什么?

​ArrayList​ 是 Java 集合框架中非常常用的一个类,位于:

java.util.ArrayList

它的底层本质是一个​动态数组​。

也就是说,ArrayList​ 内部其实维护了一个数组,用来存储元素。当元素数量超过当前数组容量时,ArrayList​ 会自动扩容,创建一个更大的数组,并把旧数组中的元素复制到新数组中。

简单理解:

ArrayList = 可以自动扩容的数组

普通数组的缺点是长度固定:

String[] arr = new String[3];

这个数组最多只能存放 3 个元素,不能自动变大。

而 ArrayList​ 可以这样使用:

ArrayList<String> list = new ArrayList<>();

list.add("Java");
list.add("Spring");
list.add("MySQL");
list.add("Redis");

即使一开始没有指定容量,它也可以不断添加元素。

二、ArrayList的使用以及其原理

1. ArrayList的创建

1.1. 无参构造方法

最常见的创建方式:

ArrayList<String> list = new ArrayList<>();

从使用者角度看,好像创建了一个空集合。

但从底层来看,ArrayList​ 并不会一开始就创建一个长度为 10 的数组。

它通常采用的是​懒加载思想​:刚创建对象时,内部数组还是一个空数组,等第一次添加元素时,才真正初始化容量。

可以简化理解为:

ArrayList<String> list = new ArrayList<>();

具体看一下无参构造的源码,其中DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}​:

 public ArrayList() {
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 }

此时:

size = 0 #存储元素的个数
elementData = 空数组

1.2. 指定初始容量

也可以这样创建:

ArrayList<String> list = new ArrayList<>(20);

这表示底层数组一开始就准备 20 个位置。

适合已经知道大概元素数量的场景。

例如:

ArrayList<Integer> ids = new ArrayList<>(1000);

如果你知道后面大概率要放 1000 个元素,那么提前指定容量可以减少扩容次数,提高性能。

根据源码可以观察到,其实是创建了一个固定大小的对象数组:

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

💡Tip

初始容量不是越大越好,而是应该根据业务预估,否则会造成内存的浪费

2. ArrayList添加元素

创建数组之后就是向其中添加元素的,通常我们使用add()​方法向ArrayList中添加元素。

list.add("爱学习的小菜鸟");

那么针对于ArrayList的元素添加,其又是怎么实现的呢?附上大师级程序员的源码。

public boolean add(E e) {
   ensureCapacityInternal(size + 1);  // 判断是否选需要扩容,确保可以容纳新元素
   elementData[size++] = e; //在末尾添加新元素
   return true; //添加成功
}

参数e​为要添加的元素,size​为ArrayList的元素个数,此时为0.

2.1 如何确保可以容纳新的元素

如果要想理解ArrayList如何确保可以容纳新的数据以及自动扩容,我们来继续跟进一下ensureCapacityInternal()​方法。

/**
 *  确保容纳指定容量的元素
 * @param minCapacity 指定容量的最小值
 */
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

/**
 *  计算容量
 * @param elementData 元素数组
 * @param minCapacity 指定容量的最小值
 * @return
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	// 判断是不是第一次添加元素(底层数组还是默认空数组)
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		// 在默认容量DEFAULT_CAPACITY = 10 和 minCapacity(1) 之间取最大值
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

其中ensureCapacityInternal()​方法中:

  • ​minCapacity​为 1 ,因为size初始值为空(int类型,即为0),所以传参 size+1 即为1。

  • ​elementData​为用于存放元素的底层数组,此时为空{}​。

所以,calculateCapacity()​方法中,if判断语句为true,最后minCapacity​的值为10。

接下来执行ensureExplicitCapacity()​方法:

/**
 * 检查集合的容量,不够时会自动扩容
 * @param minCapacity 集合所需的最小容量
 */
private void ensureExplicitCapacity(int minCapacity) {
	//记录数组修改次数,用于并发校验(modCount是fail‑fast快速失败机制核心)
    modCount++;

    //如果最小需要的容量 > 底层数组的长度,就执行真正的扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

其中ensureExplicitCapacity()​方法中,

  • ​minCapacity​为集合所需的最小容量,第一次新增元素的时候为10。

  • ​elementData.length​为底层数字的长度为0。

所以if条件成立,执行扩容函数grow()​方法:

private void grow(int minCapacity) {
    // 1. 获取旧数组长度
    int oldCapacity = elementData.length;
    // 2. 新容量 = 原容量 + 原容量/2,也就是扩容1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);

    // 3. 如果扩容1.5倍后还不满足最小需要的容量,直接使用minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;

    // 4. 校验容量是否超过ArrayList允许的最大数组容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);

    // 5. 数组拷贝:开辟新长度的数组,把旧数组数据复制过去,完成扩容
    elementData = Arrays.copyOf(elementData, newCapacity);
}

针对本次第一次添加元素的代入演算:

  1. ​oldCapacity = 0​

  2. ​newCapacity = 0 + 0 >> 1 = 0​

  3. ​0 < 10​,因此直接赋值 newCapacity = 10​

  4. 没有超出最大限制

  5. ​Arrays.copyOf​ 创建一个长度为 10 的新 Object 数组,赋值给elementData​

最后回到add()​方法:

public boolean add(E e) {
   ensureCapacityInternal(size + 1);  // 判断是否选需要扩容,确保可以容纳新元素
   elementData[size++] = e; //在末尾添加新元素
   return true; //添加成功
}

执行elementData[size++] = e​,因此,第一个元素被赋值为"爱学习的小菜鸟",然后返回true,结束第一次添加元素的方法。

3. ArrayList指定位置添加元素

除了使用add(E e)方法,也可以通过add(int index, E element)​方法向指定位置index​插入元素:

list.add(1,"菜鸟开发");

源码如下:

public void add(int index, E element) {
	rangeCheckForAdd(index);// 检查索引是否越界

	ensureCapacityInternal(size + 1);  // 判断是否选需要扩容,确保可以容纳新元素
	System.arraycopy(elementData, index, elementData, index + 1,
					 size - index); // 将index及其后面的元素往后移一位
	elementData[index] = element; //将元素插入指定位置
	size++; // 元素个数加一
}

4. 更新ArrayList中元素

使用set()​方法来更改ArrayList中元素,例如修改索引为1位置的元素内容:

list.set(1,"菜鸟开发分享");

源码如下:

public E set(int index, E element) {
	rangeCheck(index);// 索引是否越界

	E oldValue = elementData(index);// 获取指定位置的旧元素
	elementData[index] = element;// 将新元素替换到指定位置
	return oldValue; // 返回修改前指定位置的元素
}

该方法会先对指定的下标进行检查,看是否越界,然后替换新值并返回旧值。

5. 删除ArrayListz中元素

第一个方法就是remove(int index)​方法,用于删除指定索引位置的元素:

list.remove(0); //删除list数组中的第一个元素

接下来看下remove(int index)​方法的源码:

public E remove(int index) {
	rangeCheck(index); //检查是否越界

	modCount++; // 记录数组修改次数,用于并发校验(modCount是fail‑fast快速失败机制核心)
	E oldValue = elementData(index); //获取要移除位置的元素

	int numMoved = size - index - 1; // 需要移动的元素个数
	if (numMoved > 0)
		System.arraycopy(elementData, index+1, elementData, index,
						 numMoved); //移动元素
	elementData[--size] = null; // 将数组末尾的元素位置为null,让GC回收元素占用的空间

	return oldValue; //返回被删除的元素
}

第二个方法是remove(Object o)​方法,用来移除找到的第一个元素,源码如下:

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

✏️Note

  • 有相同元素的时候,只会删除第一个

泛型 2026-07-13