JDK8中ArrayList扩容机制

前言

这是基于JDK8的源码分析,在JDK6之前以及JDK11之后细节均有变动!!


首先来看ArrayList的构造方法

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {};

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData; // non-private to simplify nested class access

    private int size;

    //如果new ArrayList时指定了初始化容量大小
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 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) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            // 官方bug,如果有子类继承了AttayList并且子类中也存在一个toArray方法时会出现getClass()不为Object的情况。文章最后会给案例进行解释
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
}

上述代码中该注释的地方已经进行了注释,以上代码可以总结如下信息:

  • 对于无参构造,ArrayList会创建一个默认的空数组
  • 对于提供了初始化大小的构造器,先判断初始化大小值是否合理,如果初始化大小指定为0的情况,将另一个空数组给elementData属性。
  • 对于参数为Collection的构造器,ArrayList会将传入的列表用迭代器按顺序返回。

对于无参或是指定初始化大小为0的情况,他们真正分配容量是在第一次执行add()方法。

现在以无参构造器创建的ArrayList为例(初始化大小为0与无参创建的ArrayList扩容逻辑略有不同),接下来去查看add方法的源码进一步分析扩容逻辑

public boolean add(E e) {
    // 填加元素之前,先调用ensureCapacityInternal方法,因为要判断新添加的元素能否被装进当前数组当中,因此要进行+1
    // 这里+1的说法是我的猜想,如有不正确或是更好的说法请指出
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 执行添加元素的代码
    elementData[size++] = e;
    return true;
}

这里调用了ensureCapacityInternal方法,我们进一步查看该方法代码

// 根据给定的最小容量和当前数组元素来计算所需容量。
private static int ensureCapacityInternal(Object[] elementData, int minCapacity) {
    // 如果当前数组元素为空数组(初始情况),返回默认容量和最小容量(minCapacity此时为1)中的较大值作为所需容量
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//此时minCapacity为10
    }
    // 调用ensureExplicitCapacity方法
    ensureExplicitCapacity(minCapacity);
}

// 确保内部容量达到指定的最小容量。
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // 以最小容量减去当前数组容量,小于则扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

分析此时扩容逻辑:

  • 当第一次进入add方法时,进入ensureCapacityInternal方法中的if块,变量minCapacity被赋值为10。随后调用ensureExplicitCapacity方法,满足if条件,执行grow方法进行扩容
  • 第二次进入add方法时,进入ensureCapacityInternal方法中的if不会再满足,于是此时的minCapacity为2去调用ensureExplicitCapacity方法去调用,此时的elementData.length为10,不满足扩容条件,直接返回即可。
  • 当第十一次次执行add方法时满足ensureExplicitCapacity方法中的扩容条件,执行扩容。

接下来去查看grow方法源码,查看具体扩容逻辑

private void grow(int minCapacity) {
    // oldCapcatity为旧容量 newCapcatity为新容量
    int oldCapacity = elementData.length;
    // 每次扩容1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 当第一次扩容时,旧容量为10,新容量为0,因此交换新旧容量的值
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 当新容量大于数组最大容量时,则调用hugeCapacity方法
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    // 如果最小容量大于最大最大容量那么新容量就是Integer的最大值,否则返回数组最大容量(Integer.MAX_VALUE - 8)
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

具体的扩容逻辑注释的应该很明白了,这里就不细节分析了。

之前注释案例解释

针对第三个构造函数为什么存在toArray返回结果可能不为Object。我们来看以下案例

//继承ArrayList类,泛型随便指定,这里只是测试
public class Haha extends ArrayList<String> {
    //这里返回类型也是随便指定
    public String[] toArray(){
        return new String[]{"1","2"};
    }
}
public class Test {
    public static void main(String[] args){
        ArrayList<String> strings = new ArrayList<>();
        Object[] array = strings.toArray();
        System.out.println(array.getClass());
        strings = new Haha();
        array = strings.toArray();
        System.out.println(array.getClass());
    }
}

输出结果如下

因此在ArrayList源码中会存在将数组元素类型强转为Object部分。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zmbwcx2003

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值