Java集合源码系列之ArrayList是如何扩容的?

前言

Java中的ArrayList应该是各位Javaer很熟悉且常用的一个集合类型了,关于ArrayList的扩容规则也是面试的高频考点,下面咱就从源码的角度来分析一下ArrayList的扩容规则。

先来了解一下ArrayList的继承图,顺便提一句,ArrayList底层是用数组实现的而LinkedList底层是用链表实现的
在这里插入图片描述

接着来看看ArrayList里有哪些成员变量

// 容器的默认大小
private static final int DEFAULT_CAPACITY = 10;

// 用于保存添加的数据
transient Object[] elementData; // non-private to simplify nested class access

// 如果使用有参构造器(参数为容量),且参数等于0,则elementData使用此对象
private static final Object[] EMPTY_ELEMENTDATA = {};

// 如果使用无参构造器,则elementData使用此对象
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 当前容器里元素的个数
private int size;

接下来是ArrayList的部分构造器的源码

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


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

从这两个构造器可以看出,使用构造器时指明了容量capacity,那么会创建一个长度为capacity的Object数组,如果调用无参构造器,则会使用成员变量DEFAULTCAPACITY_EMPTY_ELEMENTDATA,长度为默认长度DEFAULT_CAPACITY = 10

既然是扩容,那么大家可以来想一下什么时候会触发扩容呢?无疑是添加元素的时候对吧!只有添加元素时才会出现数组容量不够的情况。所以,咱们来捋一捋添加元素的过程

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

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1); 
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

由源码可以看出,无论是直接添加一个元素还是在指定位置添加一个元素亦或是添加多个元素,都会进行ensureCapacityInternal(size + 1)方法调用,并将当前容器所含元素数量加1的值作为参数,那么我们跟随调用链进入该方法

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

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

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

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

在进入ensureCapacityInternal() 方法后发现,它又调用了ensureExplicitCapacity() 并且将calculateCapacity(elementData, minCapacity) 方法的返回值作为该方法的参数。从calculateCapacity方法名称可以知道,该方法的功能是计算容量,方法首先判断用来保存元素的数组是不是默认数组(使用空参构造器创建的ArrayList),如果是,返回DEFAULT_CAPACITYminCapacity中的较大值,如果不是,就返回minCapacity。DEFAULT_CAPACITY是ArrayList的成员变量,值为10。minCapacity追溯调用链可以看出最少是size + 1(如果调用addAll()函数,则可能大于1)。接着进入ensureExplicitCapacity() 方法,参数是calculateCapacity() 方法的返回值。该方法首先进行modCount++操作,modCount继承自父类AbstractList,表示列表被修改的次数(添加元素和删除元素都是修改)。当minCapacity大于当前存储元素的数组的长度,则调用grow() 方法,grow() 方法无疑就是扩容方法了,来咱们接着看

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

// Params:minCapacity – the desired minimum capacity
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);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    	MAX_ARRAY_SIZE;
}

由源码可以看出,首先得到原数组的长度,新数组的长度为原数组长度加上原数组长度的一半。接着判断新数组长度是否小于参数minCapacity(大家一定要理解这个参数minCapacity的含义,官方给的含义是—所需的最小容量,其实就是当前容器所含的元素个数加上需要添加的元素的个数),如果新长度小于minCapacity(如果使用addAll()方法一次添加多个元素,可能会发生这种情况),那么直接将minCapacity作为新长度,简单直接。那么接下来进入第二个if的判断,当ArrayList不断地扩容,最终计算出来的新长度大于常量MAX_ARRAY_SIZE(这个常量的大小是Integer.MAX_VALUE - 8,至于为什么是这个数值,大家可以去看源码的注释),则会调用hugeCapacity() 函数,验证minCapacity是不是已经大于Integer.MAX_VALUE(在Integer.MAX_VALUE的值上再加上一个正数会变成负数,结果溢出),溢出就报OutOfMemoryError,否则判断minCapacity是否已经大于MAX_ARRAY_SIZE。如果是,将新数组长度赋值为Integer.MAX_VALUE,那么接下来再添加元素,就会报OutOfMemoryError。如果不是,就将新数组长度赋值为MAX_ARRAY_SIZE

从这也可以看出ArrayList的作者是想把ArrayList的最大容量控制在MAX_ARRAY_SIZE以内。其实MAX_ARRAY_SIZE也就比Integer.MAX_VALUE少8,既然扩容到这种级别了,为什么不直接扩容到Integer.MAX_VALUE呢?笔者猜测应该是作者想尽量避免报OutOfMemoryError异常。那么进行到最后一句语句,调用Arrays.copyOf() 函数,造一个长度为newCapacity的新的数组,并将原数组的内容赋值到新数组中。

总结一下,要添加的元素的个数加上容器中所含元素个数是所需的最小容量,所需最小容量大于当前容器的容量就会触发扩容机制。将原容器的容量加上原容器容量的一半作为新容器容量的可能值。接着判断这个可能值是否大于所需最小容量,如果不是,则直接将所需最小容量作为新容器的容量。未来经过不断地扩容,这个可能值大于Integer.MAX_VALUE - 8以后,新容器容量的可能值就会根据所需最小容量的值重新调整,如果所需最小容量大于Integer.MAX_VALUE - 8,就调整为Integer.MAX_VALUE,如果不大于,就调整为Integer.MAX_VALUE - 8

至此,ArrayList的添加元素以至扩容的流程就说完了~ ~ 如果觉得有帮助到你,麻烦点个关注哦,这样博主会更有动力更新其他内容~~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值