初始容量
ArrayList有多个不同的构造函数,不同的构造函数的初始容量是不同的。快速看一下ArrayList源码里关于元素存放的几个私有属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
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 复制代码
JAVA
|
ArrayList有三个构造方法,不同的构造方法的容量是不一样的,具体可以查看JDK 源码。
- 如果不传入初始容量,就使用默认容量,并设置
elementData为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- 如果传入初始容量,会判断这个传入的值,如果大于0,就new一个新的Object数组,如果等于0,就直接设置
elementData为EMPTY_ELEMENTDATA。
- 如果传入一个Collection,则会调用
toArray()方法把它变成一个数组并赋值给elementData。同样会判断它的长度是否为0,如果为0,设置elementData为EMPTY_ELEMENTDATA。
扩容具体指的是什么?
可以看到,ArrayList里面有两个概念,一个是capacity,它表示的就是“容量”,其实质是数组elementData的长度。而size则表示的“存放的元素的个数”。
因为Java中,数组操作不能越界,所以我们必须要保证在插入操作的时候,不会抛出数组越界异常。
ArrayList是如何实现扩容的?
底层主要是这三个私有方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| private Object[] grow() { return grow(size + 1); }
private Object[] grow(int minCapacity) { return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity)); }
private int newCapacity(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity <= 0) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) return Math.max(DEFAULT_CAPACITY, minCapacity); if (minCapacity < 0) throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); } 复制代码
SCSS
|
可以看到,底层其实是调用了Arrays.copyOf方法来进行扩充数组容量的。这里我们主要看一下最后一个方法newCapacity(int minCapacity)的实现。
默认情况下,新的容量会是原容量的1.5倍,这里用了位运算提高效率。一般情况下,如果扩容1.5倍后就大于期望容量,那就返回这个1.5倍旧容量的值。而如果小于期望容量,那就返回期望容量。这里对默认容量10做了特殊处理。
使用1.5倍这个数值而不是直接使用期望容量,是为了防止频繁扩容影响性能。试想如果每次add操作都要扩容一次,那性能将会非常低下。
上述grow方法其实主要是用于实现自动扩容的。而用户也可以通过调用以下方法实现手动扩容:
1 2 3 4 5 6 7 8 9 10 11
| public void ensureCapacity(int minCapacity) { if (minCapacity > elementData.length && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA && minCapacity <= DEFAULT_CAPACITY)) { modCount++; grow(minCapacity); } } 复制代码
SCSS
|
为什么需要手动扩容?试想一下,如果用户已经知道即将存入大量的元素,比如目前有20个元素,即将存入2000个。那这个时候使用自动扩容就会扩容多次。而手动扩容可以一次性扩容到2000,可以提高性能。
ArrayList有缩容吗?
ArrayList没有缩容。无论是remove方法还是clear方法,它们都不会改变现有数组elementData的长度。但是它们都会把相应位置的元素设置为null,以便垃圾收集器回收掉不使用的元素,节省内存。