ArrayList动态扩容
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
阅读源码时,看到ArrayList 的一个无参构造函数的注释 “构造一个初始容量为10的空的集合”,找不到哪里有10这个数字,很懵逼。
跟 elementData 有关系?
transient Object[] elementData; // non-private to simplify nested class access
elementData 是ArratList 的成员变量,初始值是空的Object数组(transient 是声明这个属性不能被序列化,以后再研究)
跟 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 有关系?
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
它也是个空数组,合着这个无参构造方法,就是将elementData 指向另一个空数组。
继续阅读源码,看到add方法
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
add方法需要先对数组扩容,再向里面填充元素,可见是ensureCapacityInternal 方法,扩容了数组。传入的 size 是当前集合的长度
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
方法中,先对 elementData 进行了判断,如果是空数组的话,让 minCapacity =10
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
ensureExplicitCapacity 方法将 modCount +1,modCount 是ArrayList 继承自 AbstractList 的属性,它是用来记录集合的尺寸被修改次数的,add、remove 方法会导致 modCount +1 ,而set 方法不会,因为set 方法没有改变集合的尺寸。(modCount 的作用会在fail-fast(快速失败) 中体现,在我上一篇文章CocurrentModificationException 中有说到)
然后判断当前数组需要的最小容量 是否大于当前数组的长度,如果大于则扩容
看到这里,可以知道,ArrayList 是通过 grow 方法进行扩容的。
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
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);
}
oldCapacity 是原数组的长度,newCapacity 是原数组的长度 * 1.5 倍(参考:https://blog.csdn.net/zzti_erlie/article/details/80204053)
符号 | 例子 | 解释 |
---|---|---|
<< | num<< n | 相当于 num×(2^n),算数左移(逻辑左移) |
>> | num>>n | 相当于num / (2^n),算数右移 |
>>> | num>>>n | 逻辑右移,当num为正数和算术右移一个效果 |
所以 int newCapacity = 1.5 * oldCapacity
if (newCapacity - minCapacity < 0) , 如果 newCapacity 还是小于最小容量,那就将原数组扩容到最小容量的尺寸,这种情况一般在1、通过addAll一次性添加多个元素 2、原数组为空数组时 会出现。通过add方法一般只会让 ArrayList 每次扩容为原数组的1.5倍。
if (newCapacity - MAX_ARRAY_SIZE > 0) ,如果 newCapacity 大于数组最大容量,说明原数组长度已经很大了
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError(); //minCapacity大于int的最大值,内存溢出异常
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
如果此时通过addAll 方法一次性添加很多元素,导致数组需要的最小容量大于数组的最大容量,则将数组扩容至 Integer 的最大值,否则将数组扩容至数组最大容量。
grow方法扩容实际上是调用了 Arrays.copyOf 方法,而 Arrays.copyOf 的内部其实调用了 System.arraycopy() ,System.arraycopy() 方法是将原数组复制到一个容量被扩容过的新的数组中,再将新数组返回。如果需要向数组中通过add方法添加很多元素的话,可能会导致多次创建新数组,效率很低。
/**
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
ArrayList 中有一个共有方法,程序猿可以通过调用该方法,将集合扩容至自己想要的大小,不需要通过grow多次扩容,提高效率。此处参考(https://blog.csdn.net/weixin_42762133/article/details/97927430)
最后再看看ArrayList 的有参构造
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);
}
}
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;
}
}
如果有参构造传入的是数字,initialCapacity 如果大于0,则初始化 elementData 为长度为 initialCapacity 的数组,如果等于0,则指向一个空数组,如果小于0则抛出异常。
如果有参构造传入的是Collection ,则将c转换为数组,对长度进行判断,如果=0,指向空数组,如果不为0,进行以下的 if 判断
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
以上代码的目的是,因为 ArrayList 可以存放多种不同类型的数据 ,将有参构造传入的数组转换为 Object 类型数组 , 方便操作,而且 elementData 声明时就是 Object 类型数组。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
参考博客
[1]https://blog.csdn.net/zymx14/article/details/78324464
[2]https://blog.csdn.net/zzti_erlie/article/details/80204053
[3]https://blog.csdn.net/weixin_42762133/article/details/97927430