Java之ArrayList源码分析(第四篇:扩容机制)

手动触发的扩充数组容量的方法ensureCapacity()

ensureCapacity(int)方法分析

    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

检查当前数组容量能否再添加一个新的元素,传入参数minCapacity表示预期的数组最小容量,这个用法可以在添加大量的元素前,提前手动扩容,有利于添加元素的性能!

1、确认扩充的最小值

检查当前ArrayList对象持有的底层数组对象elementData是否指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA对象,当使用默认构造方法ArrayList()创建对象时,elementData指向该对象。默认构造方法ArrayList()创建的对象,局部变量minExpand保存默认值为10(常量DEFAULT_CAPACITY的值),否则minExpand持有值为0!minExpand表示的是扩充容量的最小基数值

2、判断是否需要扩容

传入的预期的数组最小容量minCapacity值与当前扩容的最小基数值minExpand进行比较,只有预期的数组最小容量值minCapacity大于最小基数值minExpand时,才需要调用ensureExplicitCapacity()方法进行扩容,并将预期的扩容值minCapacity直接传入。对于预期的扩容值小于最小基数值,完全没必要执行扩容操作!

ensureExplicitCapacity(int)方法分析

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

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

用于检查数组容量的方法,当我们主动进行扩容、或者每次添加元素被动扩容,都会调用到此方法,传入的参数minCapacity表示预期的数组最小容量值

1、fail-fast机制

将实例变量modCount增加1,表示ArrayList产生修改行为,防止ArrayList在多线程下使用

2、检查是否需要扩容

预期的数组最小容量值minCapacity与当前ArrayList对象持有的数组对象elementData的长度比较,如果预期的数组最小容量值大于当前数组对象elementData的长度,他们相减一定会大于0,说明底层数组无法存储更多的元素,此时会将局部变量minCapacity传入grow()方法中进行数组容量的扩充

grow(int)方法分析

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

实际执行底层数组扩充容量的方法,传入的参数minCapacity表示预期的数组最小容量

1、获取当前数组的容量

获取ArrayList对象持有的数组对象elementData的长度(容量),并赋值给局部变量oldCapacity

2、计算新的数组容量

将旧的数组容量与旧的数组容量的一半相加(右移1位表示除以2),新的数组容量值赋值给局部变量newCapacity,newCapacity表示新的数组容量,新的数组容量为原数组容量的1.5倍,比如原数组容量为10,那么新的数组容量则为10 + 10/2,即新的数组容量为15

3、检查新的数组容量能否满足预期容量的需求,不满足则直接使用预期容量值作为新的数组容量

1.5倍的新容量可能还是不够预期的数组最小容量(容错),此时将新的数组容量newCapacity与需要的数组最小容量minCapacity比较,如果新的数组容量newCapacity小于预期的数组最小容量minCapacity,此时将直接使用预期的数组最小容量minCapacity直接作为局部变量newCapacity的容量值

4、检查新的数组容量是否超过最大值

如果新的数组容量newCapacity超过常量MAX_ARRAY_SIZE值,minCapacity传入到一个hugeCapacity方法中,这个方法的返回值会作为新的数组容量newCapacity的值

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

5、当前数组中的元素全部复制到新的数组中,并且elementData指向新的数组

经过一番保护,终于到了把原数组中的所有元素都复制到新数组中了,同时新的数组对象又会赋值给elementData,此时ArrayList对象持有一个新容量的数组对象。旧数组中的元素复制、新数组的创建,则由一个Arrays的静态方法copyOf()直接完成,它接受两个参数

第一个参数表示源数组对象

第二个参数表示一个新容量

在Arrays的静态方法copyOf()内部会将旧数组中的所有元素都复制到新创建的指定容量的数组对象中,最后返回新的数组对象

hugeCapacity(int)方法分析

    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表示预期需要的最小容量

1、检查预期容量值是否小于0

当传入minCapacity小于0时,直接抛出OutOfMemoryError异常对象

2、进一步判断预期容量是否超出最大值

对minCapacity的值进一步判断,如果大于常量MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)值,则会返回一个Integer.MAX_VALUE,否则返回的是MAX_ARRAY_SIZE

public static final int MAX_VALUE = 2147483647; // 0x7fffffff

Arrays中的copyOf()方法分析

    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

用于从源数组中复制元素到新创建的数组中,并返回新创建的数组对象,传入的参数original表示源数组对象,参数newLength表示新的数组对象的长度

1、调用3个参数的重载方法

方法内部又调用了一个重载的copyOf方法进行复制与创建工作

2、向调用者返回新的数组对象

新的数组对象持有元素类型与旧数组对象一模一样!

Arrays的copyOf(U[],int,Class)方法分析

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

用于创建新的数组对象,用于将源数组中的元素复制到新的数组对象中,传入的original表示源数组对象,传入的参数newLength表示新创建的数组对象的长度,传入的newType表示数组对象持有的元素类型

这里的英文解答很详细,后面我仔细研读以下,我发现上面有坑啊……,强制转换的路子,头回见java - Do not understand the source code of Arrays.copyOf - Stack Overflow 

总结

1、ArrayList持有的底层数组对象的默认容量为10

2、扩容操作也有fail-fast机制的保护,所以仍然将modCount增加了1,防止你在多线程下使用ArrayList

3、数组扩充容量的时候,正常情况下,新的数组对象的容量是旧数组对象的容量的1.5倍,除非1.5倍的容量仍然不够用

4、Arrays的静态方法copyOf()牛逼……不仅创建新的数组对象(元素类型相同),还将旧数组对象持有的元素全部复制到新的数组对象中

5、添加一个或多个元素时,ArrayList对象持有的底层数组满足不了需求则会自动执行扩容机制,相关的底层方法与本文分析的一样

6、手动调用ensureCapacity()方法可提前触发扩容,这个方法的作用是为了性能,如果再到添加元素时去扩容效率的话,性能差点意思,如果你准确的知道要添加的元素数量根本在当前ArrayList中放不下,手动扩容真的提高了添加元素时的性能!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值