ArrayList扩容机制解析

  • Post author:
  • Post category:其他




1.ArrayList的成员变量

首先我们先了解一下ArrayList的成员变量。

// 默认初始化大小
private static final int DEFAULT_CAPACITY = 10;

// 空数组(用于空实例)
// 比如List<String> ls = new ArrayList<>(0);
private static final Object[] EMPTY_ELEMENTDATA = {};

// 主要用来标识该ArrayList使用无参构造方法进行了初始化
// elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA意味着使用无参构造函数进行了初始化
// 使用无参构造函数的默认容量是10,但是并不是一开始就进行了初始化,而是真正在插入元素的时候才进行了初始化
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 实际上保存ArrayList数据的数组
transient Object[] elementData; // non-private to simplify nested class access

// ArrayList包含的元素个数
private int size;



2.ArrayList的构造方法

了解了基本成员变量以后我们再来看一下构造函数

// 有参构造方法,由自己进行容量的初始化
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 初始化值大于0,创建initialCapacity大小的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 初始化值等于0,则创建空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // 初始化值小于0,抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}


// DEFAULTCAPACITY_EMPTY_ELEMENTDATA为0.初始化为10
// 意味着ArrayList的初始其实是空数组,当添加第一个元素的时候数组容量才变成10
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}


// 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // 如果elementData不是Object类型数据(c.toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        this.elementData = EMPTY_ELEMENTDATA;
    }
}



3.ArrayList的扩容机制

下面正式开始讲解ArrayList的扩容机制。

什么时候开始扩容,毫无疑问,当然是添加元素,而

elementData

的容量不够的时候进行扩容,所以我们从

add()

方法起手。

public boolean add(E e) {
    // 首先是将当前size+1作为参数,然后调用ensureCapacityInternal判断是否需要进行扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 最后将元素放入容量足够未扩容/容量不够扩容过的elementData数组
    elementData[size++] = e;
    return true;
}

接下来我们跟进

ensureCapacityInternal

方法,看看到底发生了什么…

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


private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 1.计算最小扩容量
    
    // 在前面也说过,如果elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    // 就意味着该ArrayList刚刚调用完无参构造方法,这个地方正是该ArrayList第一次添加元素,将容量初始化为10的地方
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 返回最小扩容量
    return minCapacity;
}


private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 2.判断是否需要进行扩容
    // 如果最小需要的容量大于数组elementData的容量,就意味着要进行扩容操作
    // 注意!不要把size和elementData的容量弄混了,一个是ArrayList当前存放元素的个数,一个是当前容量的大小!
    if (minCapacity - elementData.length > 0)
    // 3.如果判断需要进行扩容,则调用grow方法进行扩容
        grow(minCapacity);
}

通过以上几个方法的执行,如果确定最小需求容量

minCapacity

大于该ArrayList的当前容量,就会调用

grow()

方法进行扩容,我们再继续看

grow()

方法的内部实现。

private void grow(int minCapacity) {
    
    // 先获取旧容量oldCapacity
    // overflow-conscious code
    int oldCapacity = elementData.length;
    
    // 采用右移计算(效率更高),将新容量newCapacity赋值为旧容量oldCapacity的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 检查新容量是否符合最小容量需求,如果新容量小于最小容量需求,就将新容量设计为最小容量需求
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
    // 再检查新容量newCapacity是否超出了ArrayList类所定义的最大容量
    // 如果超出了那么就将新容量设置为Integer的最大值,否则设置为Arraylist定义的最大容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    
    // 最后将elementData按照新容量newCapcity拷贝到一个新数组返回
    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;
}

读到这里,可能你会好奇关于为什么ArrayList的默认最大容量为

Integer.MAX_VALUE - 8

?我们可以详细看一下关于这个变量的注解

/**
 * The maximum size of array to allocate.
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
 
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

大概意思是说,一些虚拟机会预留数组头部的大小,一般数组头部有8个字节,所以 这里要减掉头部的8个字节,不然的话,整个数组大小就超过int的最大值了。就会跑出OutOfMemoryError错误。



4.总结

最后我们总结一下整个扩容过程

在这里插入图片描述

画图不易,点赞支持一下!



版权声明:本文为qq_52002412原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。