Analysis of ArrayList Expansion Mechanism

Analysis of ArrayList Expansion Mechanism

  1. First look at the constructor of ArrayList:
/**
     * 默认初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;


    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };

    /**
     *默认构造函数,使用初始容量10构造一个空列表(无参数构造)
     */
    public ArrayList() {
    
    
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 带初始容量参数的构造函数。(用户自己指定容量)
     */
    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。
    */
     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;
        }
    }

From the source code of the constructor, it can be found that when an ArrayList is created with a parameterless constructor, an empty array is actually initialized and assigned. The capacity is actually allocated when the operation of adding elements to the array is actually performed. That is, when the first element is added to the array, the capacity of the array is expanded to 10.

  1. The following takes the ArrayList created by the no-argument constructor as an example:
  • add method:
/**
     * 将指定的元素追加到此列表的末尾。
     */
    public boolean add(E e) {
    
    
   //添加元素之前,先调用ensureCapacityInternal方法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //这里看到ArrayList添加元素的实质就相当于为数组赋值
        elementData[size++] = e;
        return true;
    }

Note: JDK11 removed ensureCapacityInternal() and ensureExplicitCapacity() methods

Let's take a look at the method add()in the method ensureCapacityInternal() :

   //得到最小扩容量
    private void ensureCapacityInternal(int minCapacity) {
    
    
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
              // 获取默认的容量和传入参数的较大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

So, when the 1st element is added, minCapacity is 1, and after Math.max() method comparison, minCapacity is 10.

ensureCapacityInternal() Look at the method in the method again ensureExplicitCapacity():

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

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

analyze:

  • When we add the first element to ArrayList, elementData.length is 0 (an empty list), and ensureCapacityInternal() the method , so minCapacity is 10 at this time. At this time, minCapacity - elementData.length > 0it is established, so it will enter grow(minCapacity) the method .
  • When the second element is added, minCapacity is 2, and at this time elementData.length (capacity) is expanded to 10 after adding the first element. At this time, minCapacity - elementData.length > 0 it is not true, so it will not enter (execute) the grow(minCapacity) method.
  • When adding the 3rd, 4th... to the 10th element, the grow method will still not be executed, and the capacity of the array is 10.

Until the 11th element is added, minCapacity is 11, and elementData.length is 10. At this point, minCapacity - elementData.length > 0 it is established and enters the grow method to expand.

ensureExplicitCapacity()Look at the method in the method again grow():

  /**
     * 要分配的最大数组大小
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * ArrayList扩容的核心方法。
     */
    private void grow(int minCapacity) {
    
    
        // 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);
    }

In the code: int newCapacity = oldCapacity + (oldCapacity >> 1), so the capacity of ArrayList will become about 1.5 times after each expansion (oldCapacity is an even number, it is 1.5 times, otherwise it is about 1.5 times), for example:10 + 10/2 = 15 ; 15 + 15/2 = 22。

Analyze grow()the method:

  • When adding the 11th element into the grow method, at this time, newCapacity is 15, which is greater than minCapacity (11), which newCapacity - minCapacity < 0is not true. At the same time newCapacity - MAX_ARRAY_SIZE > 0, it is not true, that is, the new capacity is not greater than the maximum size of the array, and it will not enter the hugeCapacity method. The capacity of the array is expanded to 15, return true in the add method, and the size is increased to 11.

grow()Look at the method in the method again hugeCapacity():
From the grow() method, we can know: If the new capacity newCapacity is greater than MAX_ARRAY_SIZE, then enter the hugeCapacity()method, by comparing minCapacityand MAX_ARRAY_SIZE, if so minCapacity > MAX_ARRAY_SIZE, the new capacity is Integer.MAX_VALUE, otherwise, the new capacity is MAX_ARRAY_SIZE That is 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作为新数组的大小
        //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

By reading the source code of ArrayList, we can find that these two methods are called in a large number in ArrayList: System.arraycopy()and Arrays.copyOf()method .
Let's take a look at the source code and usage scenarios of these two methods:

  • System.arraycopy() method:
    source code:
        // 我们发现 arraycopy 是一个 native 方法,接下来我们解释一下各个参数的具体意义
    /**
    *   复制数组
    * @param src 源数组
    * @param srcPos 源数组中的起始位置
    * @param dest 目标数组
    * @param destPos 目标数组中的起始位置
    * @param length 要复制的数组元素的数量
    */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
    
    Scenes:
        /**
     * 在此列表中的指定位置插入指定的元素。
     *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
     *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
     */
    public void add(int index, E element) {
          
          
        rangeCheckForAdd(index);
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //arraycopy()方法实现数组自己复制自己
        //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量;
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;
        size++;
    }
    
  • Arrays.copyOf() method:
    source code:
    public static int[] copyOf(int[] original, int newLength) {
          
          
        	// 申请一个新的数组
            int[] copy = new int[newLength];
    	// 调用System.arraycopy,将源数组中的数据进行拷贝,并返回新的数组
            System.arraycopy(original, 0, copy, 0,
                             Math.min(original.length, newLength));
            return copy;
        }
    

Scenes:

	 /**
	     以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。
	     */
    public Object[] toArray() {
    
    
    //elementData:要复制的数组;size:要复制的长度
        return Arrays.copyOf(elementData, size);
    }

Arrays.copyOf()The method is mainly to expand the original array.

The connection and difference between the two methods:

Contact :

It can be found through the source codes of the two that copyOf() actually calls the System.arraycopy() method internally.

Difference :
arraycopy() needs the target array, copy the original array to your own defined array or the original array, and you can choose the starting point and length of the copy and the position in the new array; copyOf() is automatically created internally by the system an array, and returns that array.

Reference: https://javaguide.cn/java/collection/arraylist-source-code.html

Guess you like

Origin blog.csdn.net/qq_44678607/article/details/130186651