ArrayList的扩容机制

初始容量

ArrayList有多个不一样的构造函数,不一样的构造函数的初始容量是不一样的。快速看一下ArrayList源码里关于元素存放的几个私有属性:java

// 默认容量是10
private static final int DEFAULT_CAPACITY = 10;
// 若是容量为0的时候,就返回这个数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 使用默认容量10时,返回这个数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 元素存放的数组
transient Object[] elementData;
// 元素的个数
private int size;

// 记录被修改的次数
protected transient int modCount = 0;
// 数组的最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
复制代码

ArrayList有三个构造方法,不一样的构造方法的容量是不同的,具体能够查看JDK 源码。数组

  • 若是不传入初始容量,就使用默认容量,并设置elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA
  • 若是传入初始容量,会判断这个传入的值,若是大于0,就new一个新的Object数组,若是等于0,就直接设置elementDataEMPTY_ELEMENTDATA
  • 若是传入一个Collection,则会调用toArray()方法把它变成一个数组并赋值给elementData。一样会判断它的长度是否为0,若是为0,设置elementDataEMPTY_ELEMENTDATA

扩容具体指的是什么?

能够看到,ArrayList里面有两个概念,一个是capacity,它表示的就是“容量”,其实质是数组elementData的长度。而size则表示的“存放的元素的个数”。函数

由于Java中,数组操做不能越界,因此咱们必需要保证在插入操做的时候,不会抛出数组越界异常。性能

ArrayList是如何实现扩容的?

底层主要是这三个私有方法:spa

// 扩容一个
private Object[] grow() {
	return grow(size + 1);
}

// 保证扩容到指望容量minCapacity及以上
private Object[] grow(int minCapacity) {
    return elementData = Arrays.copyOf(elementData,
                                       newCapacity(minCapacity));
}

// 根据指望容量minCapacity计算实际须要扩容的容量
private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length; // 获得旧容量
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 设置新容量为旧容量的1.5倍
    if (newCapacity - minCapacity <= 0) { // 若是新容量仍然小于指望容量
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // 若是是使用的默认容量
            return Math.max(DEFAULT_CAPACITY, minCapacity); // 取默认容量和指望容量较大值返回
        if (minCapacity < 0) // overflow // 检查指望容量是否越界(int 的范围)
            throw new OutOfMemoryError();
        return minCapacity; // 返回指望容量
    }
    // 若是新容量大于指望容量,判断一下新容量是否越界
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
        : hugeCapacity(minCapacity);
}
复制代码

能够看到,底层实际上是调用了Arrays.copyOf方法来进行扩充数组容量的。这里咱们主要看一下最后一个方法newCapacity(int minCapacity)的实现。code

默认状况下,新的容量会是原容量的1.5倍,这里用了位运算提升效率。通常状况下,若是扩容1.5倍后就大于指望容量,那就返回这个1.5倍旧容量的值。而若是小于指望容量,那就返回指望容量。这里对默认容量10作了特殊处理。内存

使用1.5倍这个数值而不是直接使用指望容量,是为了防止频繁扩容影响性能。试想若是每次add操做都要扩容一次,那性能将会很是低下。ci

上述grow方法其实主要是用于实现自动扩容的。而用户也能够经过调用如下方法实现手动扩容:element

public void ensureCapacity(int minCapacity) {
    if (minCapacity > elementData.length
        && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
             && minCapacity <= DEFAULT_CAPACITY)) {
        modCount++;
        grow(minCapacity);
    }
}
复制代码

为何须要手动扩容?试想一下,若是用户已经知道即将存入大量的元素,好比目前有20个元素,即将存入2000个。那这个时候使用自动扩容就会扩容屡次。而手动扩容能够一次性扩容到2000,能够提升性能。rem

ArrayList有缩容吗?

ArrayList没有缩容。不管是remove方法仍是clear方法,它们都不会改变现有数组elementData的长度。可是它们都会把相应位置的元素设置为null,以便垃圾收集器回收掉不使用的元素,节省内存。

相关文章
相关标签/搜索