简书:capo
转载请注明原创出处,谢谢!
今天,我们分析下JDK8中ArrayList的源码,我们知道ArrayList是List接口的一个实现,是一个动态扩容的数组。那么它是怎样在我们添加元素时实现动态扩容的了?
这是数组定义的几个成员变量
首先我们调用数组的 add(E e) 方法时,这里我用的是一个重载的方法,第一个参数指的是要添加元素的下标,默认如果我们不提供这个参数的话是从数组的最后添加的。
public void add(int var1, E var2) {
//检查数组下标
this.rangeCheckForAdd(var1);
//保证数组内部容量 插入数组位置往后移动一个位置
this.ensureCapacityInternal(this.size + 1);
//旧的数组就会使用Arrays.copyOf方法被复制到新的数组中,现有的数组指向了新的数组
System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1);
this.elementData[var1] = var2;
++this.size;
}
Java会检查这个数组下标是否超过这个数组的长度或者下标小于0,如果是的话就抛出
IndexOutOfBoundsException异常
private void rangeCheckForAdd(int var1) {
if(var1 > this.size || var1 < 0) {
throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));
}
}
如果数组元素等于默认空数组的话,那么数组插入数组索引下标会进行一个取最大值的操作
private void ensureCapacityInternal(int var1) {
//如果添加的这个元素 == 默认的空数组的第一个元素
if(this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
var1 = Math.max(10, var1); //在数组初始容量和数组的下标中取一个最大值
}
//明确保证数组的容量
this.ensureExplicitCapacity(var1);
}
为了明确保证数组容量序列化计算
private void ensureExplicitCapacity(int var1) {
//反序列化计算器加 1
++this.modCount;
//如果要指定添加元素的下标位置 > 数组长度 此时就要扩容
if(var1 - this.elementData.length > 0) {
this.grow(var1);
}
}
计算新数组的长度=原始数组的1.5倍
private void grow(int var1) {
int var2 = this.elementData.length;
//新的数组长度 = 原始数组长度 + 原始数组 / 2
int var3 = var2 + (var2 >> 1);
//如果新的数组长度 < 要添加元素的位置
if(var3 - var1 < 0) {
var3 = var1;//新数组长度 = 添加元素的位置长度
}
//如果新的数组长度 > 2147483639
if(var3 - 2147483639 > 0) {
var3 = hugeCapacity(var1); //就判断是否超过了JVM内存
}
this.elementData = Arrays.copyOf(this.elementData, var3);
}
判断这个新数组长度是否JVM内存分配的最大值
private static int hugeCapacity(int var0) {
if(var0 < 0) {
throw new OutOfMemoryError();
} else {
return var0 > 2147483639?2147483647:2147483639;
}
}
总结一下:
- ArrayList底层是一个动态扩容的数
- 数组的初始长度是10个单位
- 如果指定了要添加元素的索引下标的话,如果这个数组元素默认初始空数组的话就取一个最小值(添加元素的下标索引)
- 扩容后新数组的长度 = 原始数组长度 + 原始数组长度/2 (也就是原始数组长度的1.5倍)
- 数组的长度不是没有上限的,如果超过2147483639就会报OOM