ArrayList扩容分析
ArrayList是一个实现List接口的可扩容数组。通过默认构造器创建容器时,该数组先被初始化为空数组,之后在首次添加数据时再将其初始化成长度为10的数组。
public ArrayList() {//首先初始化为一个空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//首次调用add添加元素时会先进入ensureCapacityInternal()方法并且传入size+1,此时size为0
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
//进入ensureCapacityInternal()方法后
private void ensureCapacityInternal(int minCapacity) {
//用于确保ArrayList的内部数组elementData有足够的空间来存储元素。
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//随后进入calculateCapacity(),
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);//若初始大小小于10,则将size置为10
}
return minCapacity;
}
//ensureExplicitCapacity方法接受一个参数minCapacity,表示所需的最小容量。如果当前elementData的长度小于minCapacity,那么ensureExplicitCapacity方法会调用grow方法来分配一个新的数组,其长度至少为minCapacity
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//modCount是一个用来记录ArrayList结构变化次数的字段,每次添加或删除元素时,modCount都会增加。是ArrayList的一个同步机制,用于防止并发修改异常。
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
也可以使用有参构造器来创建容器,并通过参数来显式指定数组的容量。
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);
}
}
如果向ArrayList中添加数据会造成超出数组长度限制,则会触发自动扩容将旧数组中的数据拷贝到新数组里,而新数组 的长度为原来长度的1.5倍,适用于快速查找匹配场景。
ArrayList扩容机制:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
//代码int newCapacity = oldCapacity + (oldCapacity >> 1);中,oldCapacity >> 1表示将oldCapacity的二进制位全部右移1位。由于整数除法的向下取整特性,这相当于将oldCapacity除以2并取整数部分。
//这行代码的含义是:新的容量是旧的容量加上旧容量的一半。这是一种常见的动态扩容策略,用于在不增加太多内存的情况下满足容量的增长需求。
不过ArrayList没有缩容机制, 但是ArrayList提供trimToSize()
方法,它的作用是将ArrayList的容量调整为当前元素的数量。当调用此方法时,如果ArrayList的当前长度小于内部数组的长度,则会创建一个长度与元素数量相符的新数组,并将元素复制过去,释放多余的内存。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)?EMPTY_ELEMENTDATA:Arrays.copyOf(elementData,size);
}
}
这样做可以确保ArrayList不再持有多于实际需求的外部内存,有助于内存优化。