【Java】JDK 1.8 ArrayList扩容原理

两个构造器

ArrayList的以下属性,跟扩容有关。

//无参构造时,初始容量大小
private static final int DEFAULT_CAPACITY = 10;
//存放ArrayList元素的数组
transient Object[] elementData;
//ArrayList的大小
private int size;
//传参为0时构造的数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//无参构造时的“空”数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

ArrayList一共有三个构造器,这里主要提到前两个,即无参的和传入一个整数的构造器。

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

无参的很简单,直接将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋给元素数组。

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

有参的也很简单,传入的参数大于0时,创建一个新的Object数组。等于0时,将EMPTY_ELEMENTDATA赋给元素数组,小于0时,直接抛异常。

这两种方式创建的ArrayList在扩容的时候,会有细微的区别。

以下源码基于JDK 1.8

使用无参构造器的扩容机制

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

执行add方法,首先会调用ensureCapacityInteral(int minCapacity)方法。

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

这个方法的参数是minCapacity,最小容量,此时minCapacity=size+1=0+1=1。这个方法又会调用ensureExplicitCapacity(int minCapactiy)方法。在这之前会调用calculateCapacity(elementData, minCapacity)方法,利用其返回值。

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

这个方法,传入的参数是elementDataminCapactiy,如果数组是由无参构造器建造的,那么就返回默认数组大小DEFAULT_CAPACITY当前最小容量minCapacity较大值。此时minCapacity=1,默认为10,所以返回的是10。

所以,当由无参构造器构造时,如果minCapacity<10,就返回10;大于10,则返回minCapacity,也就是当前容量+1(即size+1)。

而有参构造器构成时,总是返回minCapacity,也即(size+1)。

那么此时ensureExplicitCapacity方法的入参,就是10,

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
    	grow(minCapacity);
}

由于执行了添加操作,所以modCount++(这个参数在迭代器的hasNext()next()会用到,可能会抛出ConcurrentModificationException),接着判断minCapacity当前数组长度的关系,此时minCapacity=10,数组长度为0,minCapacity-element.length>0,所以要执行grow(int minCapacity)方法,对数组进行扩容。

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    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);
}

oldCapacity=0,newCapacity=oldCapacity*1.5还是=0,所以newCapacity-minCapacity=0-1<0,把newCapacity设置为minCapacity,然后调用Arrays.copyOf方法,将原数组扩容至newCapacity,也就是10。

最后通过elementData[size++] = e,完成添加,此时size=1。


第2次(一直到第10次)添加的时候,还是先调用ensureCapacityInteral(size+1),此时传入的参数为2。

然后调用calculateCapacity(elementData, minCapacity=2),2<默认值10,还是返回10。

ensureExplicitCapacity中,由于此时数组的容量是10,而10-10=0,所以不执行grow方法。

那么直接就是elementData[size++] = e,size=2。


第11次添加的时候,依然调用ensureCapacityInteral(size+1),此时传入的参数是11。

然后调用calculateCapacity(elementData, minCapacity=11),11>默认值10,返回11。

ensureExplicitCapacity中,由于此时数组的容量是10,而11-10>0,所以又要进行扩容了。

grow方法,此时传入的参数是11,oldCapacity是10,newCapacity是15,newCapacity-minCapacity=4>0,直接复制数组,长度由10增加到15,最后完成添加。

此后以此类推。


使用有参构造器的扩容机制

如果是有参构造的,那么elementData=EMPTY_ELEMENTDATAadd的时候,依然会先调用ensureCapacityInteral(size+1),传入的参数是1。

calculateCapacity中,此时elementData并不等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,条件不成立,所以直接返回minCapacity也就是1。

那么ensureExplicitCapacity的入参,就是1,此时1-length=1>0,所以执行grow方法。

grow方法中,旧容量为0,新容量还是为0,newCapacity-minCapacity<0,触发newCapacity=minCapacity,新容量=1,然后进行数组复制,添加成功。


第2次添加的时候,ensureCapacityInteral(size+1),传入的是2。

calculateCapacity始终返回minCapacity,所以是2。

ensureExplicitCapacity的入参,是2,2-1>0,又要执行grow方法。

grow方法中,旧容量为1,新容量为1,此时1-1=0,1-2<0,新容量=2,然后添加。

之后的每一次添加,传入的minCapacity都会大于elementData.length,所以每次添加都要执行grow


区别

总的来说:

使用无参构造的ArrayList,在第一次添加后,会直接把容量扩容至10,接下来的2~10次添加,数组都不会扩容。之后一旦容量不够,就会扩容至原来的1.5倍。

而使用有参(initalCapacity=0)构造的ArrayList每一次添加都会进行扩容

这么看来,当ArrayList存储的元素数量小于等于10时,无参构造的ArrayList性能要比有参构造的稍微高那么一点点,毕竟少了复制数组的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值