超详细JDK1.8 ArrayList集合默认长度及扩容分析

1、首先看ArrayList默认构造方法创建

/**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

从以上两段代码得知,List集合底层就是用数组(属性名:elementData)存储数据的,默认构造方法初始化数组为空,那么数组的长度即为0,所以通过默认构造方法创建集合,默认数组的长度为0

2、接着看集合add方法和addAll方法

/**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
/**
     * 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(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
/**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the
     * specified collection's Iterator.  The behavior of this operation is
     * undefined if the specified collection is modified while the operation
     * is in progress.  (This implies that the behavior of this call is
     * undefined if the specified collection is this list, and this
     * list is nonempty.)
     *
     * @param c collection containing elements to be added to this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    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;
    }

add方法:调用ensureCapacityInternal,传入参数:size+1(size为集合大小(初始化默认也为0),即数组中存有数据的长度(并不是数组的长度)

addAll方法:首先将传入集合转换成数组,然后调用ensureCapacityInternal,传入参数为:size+传入数组的长度。

3、接着看ensureCapacityInternal方法:

 /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

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

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

判断elementData与DEFAULTCAPACITY_EMPTY_ELEMENTDATA这地址是否是相等的,而第一次调用add方法这两个值肯定是相等的,而DEFAULT_CAPACITY=10,minCapacity=1,所以执行minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity)得出minCapacity =10。最终得出这个方法的目的就是判断如果是第一次执行add方法,最小容量(minCapacity,可以理解为至少需要用来存数据的长度)设置为10,以后的每次进来都是size+1。

接着看调用ensureExplicitCapacity:modCount记录操作数组的次数,然后看if方法,此时判断最小容量如果大于数组长度则调用grow方法进行扩容,当第一次执行add由于elementData.length=0,所以执行grow方法参数为10

4、grow为扩容核心代码

/**
     * 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
        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;
    }

首先:新容量(注意:新容量是基于当前数组长度进行扩容)扩大到原容量(当前数组长度)的1.5倍,右移一位相当于原数值除以2。

接着:if (newCapacity - minCapacity < 0)判断新容量的大小是否小于最小容量(也就是当前数组长度扩容后还是小于最小容量,就将最小容量直接设置为新容量,否则将扩容后的容量设置为新容量,而从add方法进来正常情况不会为真(除非扩容后的容量大于了int最大值,成负数了),从addAll方法基本可能为真(当前数组的长度加上准备添加数组的长度大于扩容后的容量)),所以第一次执行add方法肯定是跳过的。

接着:if (newCapacity - MAX_ARRAY_SIZE > 0)判断新容量如果大于MAX_ARRAY_SIZE 则进行容量最大值控制,传入最小容量执行hugeCapacity方法。

如果minCapacity为负数则抛出内存溢出的异常,可是什么情况下为负数呢?,我们继续看,当最小容量超过了int最大值,就会成负数,而什么情况下会超过最大值?我这里想到了一种情况,就是在执行addAll的情况下,此时的最小容量是当前数组的长度加上准备添加数组的长度。最后返回,如果最小容量大于MAX_ARRAY_SIZE 则返回integer最大值(这里有一个疑问,文档最后描述),否则,就是MAX_ARRAY_SIZE,从而就限制了数组的最大长度。

然后执行grow方法的elementData = Arrays.copyOf(elementData, newCapacity);重新生成扩容后的数组对象。

最后回到初始add与addAll方法

add方法:接着执行elementData[size++] = e;将数据直接添加在可能扩容了也可能看没扩容后的数组中,然后size++;

addAll方法:接着执行System.arraycopy(a, 0, elementData, size, numNew);将集合数据添加在可能扩容了也可能看没扩容后的数组中,然后 size += numNew;

最后得出结论:jdk1.8在用默认构造方法创建对象的时候,数组长度为0,在第一次执行add才会将数组长度设置为10,或执行addAll方法的时候,如果添加集合长度大于10则数组长度为集合长度,否则数组长度也为10,所以一般创建集合的时候,提前给集合设置大概所需用到的容量大小(这个容量大小是指数组的长度,并不是集合的size),这样就可以在集合创建后减少执行add或addAll方法时的扩容操作,从而提升代码执行效率。

最后说一下那个疑问:

首先MAX_ARRAY_SIZE值(数组elementData的最大长度)为什么是Integer.MAX_VALUE - 8,因为数组在java里是一种特殊类型,既不是基本数据类型,也不是引用数据类型。有别于普通的“类的实例”对象,java里数组不是类,所以也就没有对应的class文件,数组类型是由jvm从元素类型合成出来的,在jvm中获取数组的长度是用字节码指令的获取的。在数组的对象头里有一个_length字段,记录数组长度,只需要去读_length字段就可以了。所以MAX_ARRAY_SIZE值为Integer最大值减8,这个8就是就是存了数组_length字段。

好,我们已经明白了为什么数组最大长度要减8了,不过这个时候又有了一个新的疑问:

就是从如果最小容量大于MAX_ARRAY_SIZE 则返回integer最大值,而数组长度不是不能为int最大值吗?这个不是和矛盾了吗?

这个疑问我也没有解开,可能是因为判断容量最大限制无需很严谨,因为一个集合这么大本身就影响效率,而且就算真的大过头了,反正也就是抛出一个内存溢出的异常,同理minCapacity<0情况的解决方式。

发布了9 篇原创文章 · 获赞 10 · 访问量 5929

猜你喜欢

转载自blog.csdn.net/zyt_java/article/details/105561380