使用 ArrayList.trimToSize 方法缩小数组容积

我们知道,ArrayList 底层是由数组实现的,当调用 add 方法插入数据如果数组空间不够时,会发生扩容,新空间大小为原来的 1.5 倍。

具体可参考源码的 grow 方法:

1
2
3
4
5
6
7
8
9
10
11
12
// 源码来自 JDK1.7
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5 倍
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);
}

那么就会产生一个问题,如果需要存储的数量刚好达到扩容而又没有更多的数据需要存储,剩下的空间岂不浪费掉了?

就如下面的代码,在第五行数组扩容为 1500,但是实际只存了 101 个元素。

1
2
3
4
5
List<Integer> list = new ArrayList<Integer>(1000);
for (int i = 0; i < 1000; i++) {
list.add(i);
}
list.add(1001); // 发生扩容,数组长度为 1500。

最近项目中就类似场景:系统启动,初始化一些数据到 ArrayList 中缓存起来,这些数据比较多(几千个元素)但是根据业务场景是不会变的。那么我想是不是也会出现如上空间浪费的问题呢?

其实 ArrayList 中是有方法实现“缩容”的,可参考 trimToSize 方法:

1
2
3
4
5
6
7
8
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}

源码很简单,就是调用 Arrays.copyOf 将容量减少到和元素数量一样大。