Java8中ArrayList的扩容机制

Java8中ArrayList的扩容机制

准备

首先要清楚ArrayList的容量的概念

The array buffer into which the elements of the ArrayList are stored. The capacity of the ArrayList is the length of this array buffer. Any empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added

这是jdk8源码中对其elementData属性给出的doc解释,意思就是说这个elementData是存储 ArrayList 元素的数组缓冲区。 ArrayList 的容量就是这个数组缓冲区的长度。这里注意ArrayList的容量并不是size属性所对应的值。

The size of the ArrayList (the number of elements it contains).

这是官方对size属性给出的解释,size代表的是ArrayList中包含的元素的个数,从后面的例子中就可以看出差别

先看结论

  1. 当使用add方法添加元素时,list扩容后的大小是上一次的大小的1.5倍(当然这里的扩容指的是经过第一次扩容后容量为10之后的再发生的扩容行为)。
  2. 当使用addAll方法添加元素时,list会进行一次性扩容,不会因为扩容后容量不够而再次扩容,最后一个容量为16的就是一个例子,他不会扩容到15然后再扩容到15 + 15>>1 = 22,而是一次到位,也就是说,使用addAll方法,没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量的1.5 倍, 实际元素个数)

使用add(E e) 方法添加

public static void main(String[] args) {
    ArrayList<Integer> list4 = new ArrayList<>();
    for (int i = 1; i <= 11; i++) {
        list4.add(i);
        System.out.println("第"+i+"次添加后list4的容量:"+length(list4));
    }

}

/**
 * 使用反射获取其elementData属性的长度即ArrayList的容量
 * @param list
 * @return
 */
public static int length(ArrayList<Integer> list) {
    try {
        Field field = ArrayList.class.getDeclaredField("elementData");
        field.setAccessible(true);
        Object[] elementData = (Object[]) field.get(list);
        return elementData.length;
    } catch (Exception e) {
        e.printStackTrace();
        return 0;
    }
}

当list第一次调用add要向其中添加一个数字0的时候,ArrayList底层会先确保在当前的size+1的情况下内部容量是否够用,够用就放进去,不够就扩容。

在这里插入图片描述

在这里插入图片描述第一次扩容因为elementData为一个长度为0的空数组,下一步会

拿着DEFAULT_CAPACITY(10)和当前size+1中的较大者去进行扩容,扩容前判断这个较大的数是否大于elementData的长度,如果大于就扩容,第一次扩容时,实现是这个样子

在这里插入图片描述然后将这个元素放入对应的位置,并且size+1,下图为此时的elementData和list,从这里就可以看出来list和elementData的区别,从而理解容量的概念。

在这里插入图片描述

当第十一次add时这时当前的size为10

在这里插入图片描述

这个时候这个方法就会在默认容量和size+1个选择较大的数字为下一步扩容做准备,进入grow方法,进行一个具体的扩容操作

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

int oldCapacity = elementData.length;  //记录当前的elementData的长度
int newCapacity = oldCapacity + (oldCapacity >> 1);  //计算新的长度,即新的长度为原来的1.5倍
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);  //数据转移复制

在这里插入图片描述

当执行完复制后可以看到elementData的长度扩大为15(即10的1.5倍 ),然后再将元素放入相应的位置上。

在这里插入图片描述

控制台也看到第十一次添加后list的容量为15。

在这里插入图片描述

使用addAll(Collection<? extends E> c)方法添加

先看一下结果

在这里插入图片描述

这里为什么会是11呢?让我们进入debug查看一下

在这里插入图片描述

同样是进入这个方法这个时候取;两者中较大者11,接着进行下一步,

在这里插入图片描述

这里看得出来计算出来新的容量为11

在这里插入图片描述

打印出来的也就是11

在这里插入图片描述那么写一个这样的例子

在这里插入图片描述

这个时候就可以看出结论来,

在这里插入图片描述

总结一下:

  1. 当使用add方法添加元素时,list扩容后的大小是上一次的大小的1.5倍(当然这里的扩容指的是经过第一次扩容后容量为10之后的再发生的扩容行为)。
  2. 当使用addAll方法添加元素时,list会进行一次性扩容,不会因为扩容后容量不够而再次扩容,最后一个容量为16的就是一个例子,他不会扩容到15然后再扩容到15 + 15>>1 = 22,而是一次到位,也就是说,使用addAll方法,没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量的1.5 倍, 实际元素个数)
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值