ArrayList的扩容机制

本文深入解析ArrayList的扩容机制,从ArrayList的构造方法开始,探讨在添加元素时如何确保容量可用,详细解读ensureCapacityInternal、calculateCapacity和ensureExplicitCapacity等关键方法的作用。分析扩容时的逻辑,包括扩容条件和扩容比例,并介绍手动扩容和缩减容量的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

阅读源码了解ArrayList的扩容机制

首先要知道ArrayList类内定义的几个静态常量,这些常量会在初始化和扩容时使用。
说明:对于Capacity我更愿意使用“容纳容量”来说明这是能够容纳的最大数目,容量一词在ArrayList中有时太容易使人误解了。

//默认容纳容量
private static final int DEFAULT_CAPACITY = 10;
//默认空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认容纳容量下的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储数据的数组,定义为Object是为了容纳泛型
transient Object[] elementData; 
//数组存储的实际内容大小
private int size;
//最大数组大小,数组为int类型的最大值-8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

Capacity和size意义不同。
如果以装鸡蛋的篮子比喻,Capacity指的是篮子最多可以容纳多少鸡蛋,而size是篮子中现有鸡蛋的真正数目。
ArrayList有三个构造方法,也需要有初步的了解,这样才能对ArrayList的扩容机制有更多的了解。

//指定容纳容量创建ArrayList
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {	//初始化容纳容量大于0
            this.elementData = new Object[initialCapacity];	//数组初始化容纳容量为指定的容纳容量
        } else if (initialCapacity == 0) {	//初始化容纳容量为0
            this.elementData = EMPTY_ELEMENTDATA;	//数组初始化为默认空数组
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);	//抛出异常,非法容纳容量
        }
    }
//无参构造方法,数组会初始化为默认容纳容量下的空数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;	
}
//数组会按照参数初始化为对应容纳容量的数组
public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();	//将参数转为数组
    if ((size = a.length) != 0) {	//判断长度是否为0
        if (c.getClass() == ArrayList.class) {	//判断是否为ArrayList
            elementData = a; //赋值数组
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);	//调用方法赋值数组
        }
    } else {
        elementData = EMPTY_ELEMENTDATA;	//长度为0初始化为默认空数组
    }
}

通过上述构造方法可知,无参时会创建空数组,有参时会根据参数类型创建对应的数组。下面以添加1个元素的add方法来说明ArrayList的具体扩容机制。

public boolean add(E e) {
		//确认容纳容量可用,传入参数为当前数组内容加1
        ensureCapacityInternal(size + 1); 
        //设置对应位置数组内容为传入内容,然后数组内容自增1 
        elementData[size++] = e;
        return true;
}

add方法先调用ensureCapacityInternal方法来保证容纳容量可用,随后在指定的数组位置赋值为传入参数,随后size自增1来添加1个元素。
然后看下ensureCapacityInternal方法。

private void ensureCapacityInternal(int minCapacity) {
	//计算容量然后判断是否需要扩容
     ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

这里可以看到这个方法里面调用了很多方法,由内向外看一下具体方法的作用。
先看calculateCapacity方法。

//计算容量,字面意思令人一头雾水,细究
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	//判断数组是否为默认容纳容量下的空数组(实际上数组如果为默认空数组结果也是一样的,因为二者实际内容相同)
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    	//是则返回默认容量和传入的最小容量两者之间的较大值
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //否则返回最小容量
    return minCapacity;
}

阅读源码可知calculateCapacity方法实际上是判断数组是否为空然后计算其对应的容量,若数组不为空则直接返回传入的最小容量。
然后来看ensureExplicitCapacity方法。

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        //判断是否需要扩容,这里的elementData.length可以认为是容纳容量
        if (minCapacity - elementData.length > 0)
        	//调用方法进行扩容
            grow(minCapacity);
}

终于到了真正的扩容方法了,一探究竟

private void grow(int minCapacity) {
    //将数组容纳容量赋值给oldCapacity
    int oldCapacity = elementData.length;
    //将数组容纳容量扩至原来的1.5倍的值赋给newCapacity
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //判断newCapacity和minCapacity的大小关系
    if (newCapacity - minCapacity < 0)
    	//newCapacity小于minCapacity则将minCapacity的值赋给minCapacity
        newCapacity = minCapacity;
    //判断newCapacity和最大数组大小的大小关系
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    	//调用hugeCapacity方法,传入参数为minCapacity,将得到的值赋给newCapacity
        newCapacity = hugeCapacity(minCapacity);
  	//调用Arrays.copyOf方法扩容数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

看hugeCapacity方法

private static int hugeCapacity(int minCapacity) {
	//判断minCapacity是否小于0,小于0证明此时数组内容已经达到int的最大容量,+1后溢出为负值了
    if (minCapacity < 0)
    	//抛出错误
        throw new OutOfMemoryError();
    //返回值为判断minCapacity是否大于最大数组大小,大于返回int的最大值,不大于返回最大数组大小
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

由以上可知,ArrayList在调用构造方法时传入int参数时数组小于10是没有实际意义的,因为ArrayList在calculateCapacity会将容纳容量改为默认容量,而在插入1个新的元素时,会先判断数组是否为空,为空则会判断默认数组大小和当前数组内容数量加1之间的大小关系返回其中的较大值,然后将其和数组长度(即为前文的容纳容量)的大小做比较,然后决定是否扩容,若扩容则会将原来容纳容量的1.5倍数值与最小所需容量做对比,再综合最大容量与原来容纳容量的1.5倍数值的大小关系结合对比得出结果,根据结果对原数组进行扩容。
在ArrayList中还提供了手动扩容和缩减容量至最小值的方法。
手动扩容的方法

public void ensureCapacity(int minCapacity) {
	//数组为空,minExpand赋值为0,不为空赋值为默认大小
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        ? 0
        : DEFAULT_CAPACITY;
	//判断minCapacity和minExpand的大小关系
    if (minCapacity > minExpand) {
    	//确认是否需要扩容
        ensureExplicitCapacity(minCapacity);
    }
}

缩减容量至最小值的方法

public void trimToSize() {
    modCount++;
    //如果数组内容数目大于数组容纳容量
    if (size < elementData.length) {
    	//若数组内容为空,缩减为空数组,否则缩减至数组内容大小
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

以上是对ArrayList扩容机制的探寻,这只是沧海一粟,详细的内容仍需细读源码方可了解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值