java源码分析(1)-ArrayList的扩容机制

106 阅读3分钟

简单介绍

ArrayList是Java通过动态数组来实现的一个集合类,可以用来存储有序集合。方便且高效,平时项目中用得非常多。

作为程序员的我们,对于这些经常用的工具,都想去了解其运行机制。所以这天,我好奇地new了一个ArrayList。通过调试,准备了解其内部实现。当然,既然是动态数组,重点自然是要去看看它的扩容机制的。我很快发现,原来没有那么难。菜鸡的我都能读得有来有回。那么下面就来记录一下,我读ArrayList的全部过程。😀

开干就完事

  • 第一步自然是要new一个 List<Integer> list = new ArrayList,ctrl然后点一下。进入其内部。

    private static final long serialVersionUID = 8683452581122892189L;
    
    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
    
    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    /**
     * 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 = {};
    
    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access
    
    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
    

    我去,看到这一堆密密麻麻的英文属实是要把我劝退,没事,简单看下。

    private static final long serialVersionUID = 8683452581122892189L;
    
    // 默认初始化容量
    private static final int DEFAULT_CAPACITY = 10;
    
    // 共享的空数组,用于空实例
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    // 也是共享的空数组,用于默认大小的空实例。用于区分是为了知道增加第一个元素时,容量需要增加多少。这个暂时不知道什么意思,后面看看。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    // 存储数据的数组,容器本体就在此了
    transient Object[] elementData; 
    
    //元素个数
    private int size;
    
  • 然后再看看构造方法,还是非常可读的呀。即使菜鸡如我,也可以一眼过

    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;
    }
    
    1. 当初始容量大小设置为正数,数组就直接new就完事,数组大小就是输入容量大小
    2. 初始容量设置为零,数组就设为EMPTY_ELEMENTDATA
    3. 如果不设置初始容量,也就是默认大小嘛,数组设为DEFAULTCAPACITY_EMPTY_ELEMNTDATA。
  • 居然是扩容,那么首先就先看一下add嘛,毕竟这里面肯定有扩容。

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

    果然,在add在赋值前,通过ensureChapacityInternal来确保容量大小是足够的,ctrl点进去看看

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
    
        ensureExplicitCapacity(minCapacity);
    }
    

    看到这里,我没想到这么快就清晰了。在第一次add时,如果是通过new ArrayList(), 数组值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在add时,比较size+1和DEFAULT_CAPACITY(10),也就是1和10比较,最后minCapacity为10。 ctrl点进去ensureExplicitCapacity看看。

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
    
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    

    这里先检验是否需要扩容,然后调用grow进行扩容

    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);
    }
    
    1. 看到这里,newCapacity = oldCapacity + (oldCapacity >> 1),也就是1.5倍左右。如果newCapacity比minCapacity大,则容量扩充为newCapacity,否则容量扩充为minCapacity。
    2. 我们就知道了,每次扩容都是以1.5倍的速度进行扩容的。如果是通过new ArrayList()的方式创建List,那么在第一次add的时候,容量不是扩容到1,而是直接扩容为10
    3. 关于hugeCapacity(minCapacity),如果minCapacity大于最大容量,则新容量则为Integer.MAX_VALUE,否则为 MAX_ARRAY_SIZE
     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
     private static int hugeCapacity(int minCapacity) {
         if (minCapacity < 0) // overflow
             throw new OutOfMemoryError();
         return (minCapacity > MAX_ARRAY_SIZE) ?
             Integer.MAX_VALUE :
             MAX_ARRAY_SIZE;
     }
    

    看到这里,我就有些疑惑了,MAX_ARRAY_SIZE不是最大容量了吗,为什么我们还有机会比这个容量更大。现在我还不是很理解。😴