Java中ArrayList的扩容机制

ArrayList作为List接口的实现类,它是一种可以根据需要动态增长的数组。在Java中标准的数组都是定长的,当一个数组被创建后,它不能再被修改长度,也就是说我们在创建数组时要确定数组所需的长度。但有时我们需要动态程序中获取数组长度,此时,我们就可以使用ArrayList来存储数据,但是它并不是线程安全的。

首先,ArrayList扩容发生在add()方法调用的时候,它是调用ensureCapacityInternal()来扩容的,通过方法calculateCapacity(elementData, minCapacity)获取需要扩容的长度,ensureExplicitCapacity方法可以判断是否需要扩容,ArrayList扩容的关键方法grow(): 获取到ArrayList中elementData数组的内存空间长度 扩容至原来的1.5倍,调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间,从此方法中我们可以清晰的看出其实ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。

ArrayList有三种方式来初始化,构造方法源码如下:

// 默认初始容量

 private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

第一种:

// 默认构造函数,使用初始容量10构造一个空列表(无参数构造) 
/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

第二种:

 // 带初始容量参数的构造函数。(用户自己指定容量)
 /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

第三种:

// 构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
// 如果指定的集合为null,throws NullPointerException。

/**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

我们发现,以无参构造方法创建ArrayList时,实际上初始化赋值的是一个空数组,当真正对数组进行添加元素操作时,才真正分配容量。级向数组中添加第一个元素时,数组容量扩为10。

        分析扩容机制:

以无参构造函数创建的 ArrayList 为例分析:

1.先看add()方法

// 将指定的元素追加到此列表的末尾
 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        // 添加元素之前,先调用ensureCapacityInternal方法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 这里看到ArrayList添加元素的实质就相当于为数组赋值
        elementData[size++] = e;
        return true;
    }

2.再看 ensureCapacityInternal() 方法

 add 方法 首先调用了 ensureCapacityInternal(size + 1)

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

ensureCapacityInternal()方法调用calculateCapacity(elementData, minCapacity)方法

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 获取默认的容量和传入参数的较大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

当 要 add 进第1个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity 为10。

3.ensureExplicitCapacity() 方法

如果调用 ensureCapacityInternal() 方法就一定会(执行)这个方法

// 判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            // 调用grow方法进行扩容,调用此方法代表已经开始扩容了
            grow(minCapacity);
    }

当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了ensureCapacityInternal() 方法 ,所以 minCapacity 此时为10。此时, minCapacity -elementData.length > 0 成立,所以会进入 grow(minCapacity) 方法。当add第2个元素时,minCapacity 为2,此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时, minCapacity - elementData.length > 0 不成立,所以不会进入 (执行) grow(minCapacity) 方法。添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大。进入grow方法进行扩容。

4.grow() 方法

※ ArrayList扩容的核心方法

// 要分配的最大数组大小
 /**
     * 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;
/**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        // oldCapacity为旧容量,newCapacity为新容量
        int oldCapacity = elementData.length;
         // 将oldCapacity 右移一位,其效果相当于oldCapacity /2,
         // 我们知道位运算的速度远远快于整除运算,整句运算式结果就是将新容量更新为旧容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量
         //那么就把最小需要容量当作数组的新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity() 方法来比较 minCapacity 和            
        // MAX_ARRAY_SIZE,
        // 如果minCapacity大于最大容量,则新容量则为Integer.MAX_VALUE,
        // 否则,新容量大小则为MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8

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

当add第1个元素时,oldCapacity 为0,经比较后第一个if判断成立,newCapacity = minCapacity(为10)。但是第二个if判断不会成立,即newCapacity 不比 MAX_ARRAY_SIZE大,则不会进入 hugeCapacity 方法。数组容量为10,add方法中 return true,size增为1。

当add第11个元素进入grow方法时,newCapacity为15,比minCapacity(为11)大,第一个if判断不成立。新容量没有大于数组最大size,不会进入hugeCapacity方法。数组容量扩为15,add方法中return true,size增为11。

5. hugeCapacity()方法

从上面 grow() 方法源码我们知道:如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity() 方法来比较minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量则为 Integer.MAX_VALUE ,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8 。

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // 对minCapacity和MAX_ARRAY_SIZE进行比较
        // 若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
        // 若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

最后, ArrayList 中调用Arrays.copyOf()方法进行对数组进行重新分配空间和复制原有数组内容。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值