用 ArrayList 模拟手动数组扩容(底层原理复刻)

思路说明

ArrayList 底层是 Object[],核心扩容逻辑:

  1. 初始空数组,添加元素时判断容量是否足够
  2. 不够则新建1.5倍容量的新数组
  3. 通过数组拷贝 System.arraycopy 把旧数据迁移到新数组
  4. 替换底层数组引用

下面自己手写简易动态数组,模仿 ArrayList 扩容逻辑,不直接调用原生 ArrayList,展示扩容底层实现。

完整代码

public class MyArrayList<E> {
    // 底层存储数组
    private Object[] elementData;
    // 当前元素个数
    private int size;
    // 默认初始容量
    private static final int DEFAULT_CAPACITY = 10;
    // 空数组常量
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 无参构造,初始为空数组
    public MyArrayList() {
        elementData = EMPTY_ELEMENTDATA;
    }

    // 添加元素(核心扩容逻辑在这里)
    public boolean add(E e) {
        // 1. 判断是否需要扩容
        ensureCapacityInternal(size + 1);
        // 2. 存入元素
        elementData[size++] = e;
        return true;
    }

    // 确保容量足够,不足则扩容
    private void ensureCapacityInternal(int minCapacity) {
        // 如果是空数组,第一次添加直接扩容到默认容量10
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        // 当前数组容量
        int oldCapacity = elementData.length;
        // 需要扩容
        if (minCapacity > oldCapacity) {
            grow(minCapacity);
        }
    }

    // 真正扩容方法:新容量 = 旧容量 * 1.5
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 扩容1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 若1.5倍还是不够,直接使用需要的最小容量
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        // 数组拷贝,生成新数组
        elementData = copyOf(elementData, newCapacity);
    }

    // 模拟 Arrays.copyOf 数组复制
    private Object[] copyOf(Object[] oldArr, int newLen) {
        Object[] newArr = new Object[newLen];
        // 复制旧数组所有元素到新数组
        System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
        return newArr;
    }

    // 根据下标获取元素
    @SuppressWarnings("unchecked")
    public E get(int index) {
        checkIndex(index);
        return (E) elementData[index];
    }

    // 校验下标
    private void checkIndex(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("下标越界:" + index);
        }
    }

    // 获取当前元素数量
    public int size() {
        return size;
    }

    // 打印所有元素
    public void printAll() {
        for (int i = 0; i < size; i++) {
            System.out.print(elementData[i] + " ");
        }
        System.out.println();
    }

    // 测试主方法
    public static void main(String[] args) {
        MyArrayList<Integer> list = new MyArrayList<>();

        // 添加15个元素,触发扩容(初始10,加到第11个时扩容到15)
        for (int i = 1; i <= 15; i++) {
            list.add(i);
            System.out.println("添加第" + i + "个元素,当前元素总数:" + list.size());
        }

        System.out.print("集合所有元素:");
        list.printAll();
        System.out.println("下标5的元素:" + list.get(5));
    }
}

运行结果说明

  1. 前10个元素:底层数组长度=10,不扩容
  2. 添加第11个元素时:minCapacity=11,旧容量10不足,触发扩容
    • 新容量 = 10 + 10/2 = 15
    • 创建长度15的新数组,复制旧数据
  3. 最多存15个元素,不会再次扩容,直到超过15个才会再次1.5倍扩容

如果要求:直接使用原生 ArrayList 演示扩容现象

原生 ArrayList 无法直接获取底层数组长度,可通过反射查看容量,代码:

import java.util.ArrayList;
import java.lang.reflect.Field;

public class ArrayListExpandDemo {
    public static void main(String[] args) throws Exception {
        ArrayList<Integer> list = new ArrayList<>();
        // 反射获取底层数组 elementData
        Field field = ArrayList.class.getDeclaredField("elementData");
        field.setAccessible(true);

        for (int i = 1; i <= 12; i++) {
            list.add(i);
            Object[] arr = (Object[]) field.get(list);
            System.out.println("添加" + i + ",数组容量:" + arr.length + ",元素个数size:" + list.size());
        }
    }
}

输出:

添加1,数组容量:10,元素个数size:1
...
添加10,数组容量:10,元素个数size:10
添加11,数组容量:15,元素个数size:11
添加12,数组容量:15,元素个数size:12

直观看到:存满10个后,第11个元素触发1.5倍扩容到15。

核心扩容关键点总结

  1. 扩容倍数:oldCapacity + oldCapacity >> 1 = 原容量 * 1.5
  2. 扩容本质:新建更大数组 + System.arraycopy 拷贝数据
  3. 空集合第一次add,直接分配容量10
  4. 若1.5倍容量仍不够存放当前数据,直接使用需要的最小容量
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐