从源码角度解析ArrayList扩容的原理

从源码角度解析ArrayList扩容的原理

1、ArrayList的构造方法

看源码先看构造方法,我们首先看看ArrayList 的构造方法,它有三个构造方法:

public ArrayList(int initialCapacity);	//传入一个数组的大小
public ArrayList();	//空参构造
public ArrayList(Collection<? extends E> c);	//传入一个Collection集合

对于这三个构造方法,我们分别来看看它的源码吧,我们首先来看看第一个:

    public ArrayList(int initialCapacity) {
    
    
      	//判断大小是否大于0
        if (initialCapacity > 0) {
    
    
          	//大于0则新建一个大小为initialCapacity的Object数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
    
    
          	//如果等于0则给它赋值一个空的数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
    
    	
          	//小于0则抛异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

对于这个构造方法,首先当initialCapacity 大于0时,就会去创建一个大小为initialCapacity 的Object数组。如果等于0,那就会给数组赋EMPTY_ELEMENTDATA ,我们可以来看看这个EMPTY_ELEMENTDATA 是什么东西:

private static final Object[] EMPTY_ELEMENTDATA = {
    
    };

其实这个EMPTY_ELEMENTDATA 就是一个空的数组。

再来看看无参的构造方法:

	public ArrayList() {
    
    
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }	

这个无参的构造方法直接给数组赋了一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,其实这个DEFAULTCAPACITY_EMPTY_ELEMENTDATA 也是一个空数组,我们可以看看源码里面对该常量的定义:

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };

对于第三个构造方法,就是把Collection 转化为一个数组而已:

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

2、ArrayList的add方法

上面看完了构造方法后,我们知道了如果初始化ArrayList 时是空参或者传入的大小为0时,就会初始化一个空的数组 ;如果大于0则给它新建一个对应大小的Object 数组,接下来我们来看看add(E e) 方法吧

    public boolean add(E e) {
    
    
      	//扩容关键的一步
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

这个方法里面比较关键的就是这个ensureCapacityInternal() 方法了,后面的步骤也就是给他赋值而已,没什么关键的。所以我们来看看这个ensureCapacityInternal(size+1) 方法吧。

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

这个方法调用了两个方法,一个是calculateCapacity(Object[] elementData, int minCapacity) ,另一个是ensureExplicitCapacity ,我们先来看看括号内的那个calculateCapacity(Object[] elementData, int minCapacity)方法:

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
    
      	//如果是空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
          	//private static final int DEFAULT_CAPACITY = 10;
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
      	//不为空则返回minCapacity
        return minCapacity;
    }

我们可以看到这个方法是计算数组容量的一个方法,传入的minCapacity 是一个add() 方法里面传入的插入数组后的大小。当数组为空时(就是我们前面传入空参或者为0时)就Math.max(默认大小, minCapacity) ,即使取默认大小与数组所需的最小容量的最大值。;如果不为空则直接返回了。

我们再看看ensureExplicitCapacity 方法:

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

        // 如果数组所需的最小容量 > 数组的长度
        if (minCapacity - elementData.length > 0)
          	//扩容
            grow(minCapacity);
    }

可以看到如果数组所需的最小容量大于数组的长度的时候,ArrayList 就会扩容了,再看看grow 方法:

    private void grow(int minCapacity) {
    
    
        // 获取当前数组的长度
        int oldCapacity = elementData.length;
      
      	//oldCapacity >> 1 = oldCapacity/2,即使oldCapacity + oldCapacity/2。
      	//增加了原来的一半,即扩容后是原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
      	//如果扩容后还小于所需的容量
        if (newCapacity - minCapacity < 0)
          	//直接将所需容量复制给新的大小
            newCapacity = minCapacity;
      	//如果扩容后的大小大于最大值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 把老数组copy到新数组中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

所以在这个扩容函数里面主要就是有三步:

  • 增加了原来的一半,即扩容后是原来的1.5倍
  • 如果扩容后还小于所需的容量,那么直接把容量变为所需的最小容量
  • 把老数组copy到新数组中

3、总结

总结来说就是:

  • 初始化ArrayList 时,如果指定了初始的大小,那么就初始化相应大小的Object 数组;如果没有指定大小,就初始化空数组,默认大小是10。
  • 扩容时,增加了原来的一半,即扩容后的大小是原来的1.5倍。

猜你喜欢

转载自blog.csdn.net/qq_14810195/article/details/107835008