ArrayList的变量
主要为以下五个
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;//用来计算元素个数,作用是来判断是否扩容
ArrayList的默认构造函数
//无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
在创建ArrayList的时候,如果不主动设置初始化容量,那么ArrayList就会调用无参构造进行创建。
在无参构造调用时,会把DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组赋值给实际用来装元素的数组elementData。
注意
ArrayList对象创建来出来后, 在没有调用add方法添加元素前, elementData数组就是一个空数组,并没有对数组进行任何的扩容操作。
调用add()方法添加第一个元素
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
我们可以看到,在调用add方法添加元素时,并不会立即地将元素添加到elementData数组中,而是先会调用一个ensureCapacityInternal()方法,并且将变量size+1。
size在上文提到的是ArrayList的一个变量,用来计算真正地添加元素到数组中前,ArrayList所需要的实际最小的容量大小。
例如
上文中我们只是用无参构造创建了一个ArrayList,并没有对容量进行实际扩容或者初始化,这时候size = 0,因为源代码中size并没有设置参数,所以默认为0。然后我们调用add方法,进入方法体中,会调用ensureCapacityInternal(size + 1),这里的size+1是因为我们即将添加一个元素,那么数组实际所需的最小容量应该为0+1=1。
如果当前ArrayList已经存在了五个元素,当调用add()方法添加第六个元素,那么此时所需要的实际容量size就应该5+1=6。(只要把这个size,也就是方法中的minCapacity--------实际所需的最小容量理解了,后面扩容就懂了百分之八十了。)
ensureCapacityInternal()方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
该方法会调用ensureExplicitCapacity()方法,我们可以注意到此方法的参数,又通过调用一个calculateCapacity()方法来得到。
calculateCapacity()方法
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
该方法目的从源代码来看就是判断你是否用无参构造创建的ArrayList,然后返回一个实际所需的容量。如果是,在第一次使用add()方法添加元素时。就会返回DEFAULT_CAPACITY=10的容量。
上文中提到,在调用无参构造时,会把DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组赋值给实际用来装元素的数组elementData。
所以使用无参构造创建ArrayList,这里的if判断为true,通过Math.max进行判断,如果minCapacity比默认容量DEFAULT_CAPACITY=10小,那么就会返回默认容量DEFAULT_CAPACITY=10,反之则返回minCapacity。之后再次添加元素后再次进入该方法,就不会再进入if判断,因为elementData已经不为空,直接返回minCapacity。
ensureExplicitCapacity()方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
得到了当前数组实际所需的容量大小minCapacity后,进入此方法。
如果minCapacity比数组长度大,那说明数组的容量已经不够,需要扩容,接下来就会进入到真正的扩容方法 grow()中。
反之,则说明当前数组的容量够,就不会进行扩容。,然后就会回到add()方法,将size永久+1,然后添加元素。
grow()方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;//将当前数组的长度赋值给旧容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//新的容量
情况1 if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
情况2 if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
情况3
elementData = Arrays.copyOf(elementData, newCapacity);
}
>>是位运算符,将十进制转化为二进制,向左移。源代码中是向左移一位。
具体快速运算就是偶数/2,奇数-1/2。
例如 10>>1 == 10/2=5; 29>>1 == (29-1)/2=14。
这里的扩容就是旧容量扩容1.5倍。
情况1:
能进入到情况1,要么是使用默认构造,第一次调用add()方法,数组长度为0,oldCapacity则为0,newCapacity无论怎么运算也为0,这时候就会进入情况1,把newCapacity赋值为默认容量10。
要么是使用了第三种构造方法,将其他的collection接口下的集合装到ArrayList下。如果元素足够多,那么oldCapacity扩容1.5倍得到newCapacity,那也装不下,就会直接让newCapacity=实际所需的最小容量。
情况2:
不太可能遇到,因为你所需要的最小容量已经比Integer的最大值-8都还大了。
情况3:
就是你一个个添加元素,超过当前数组长度的话,把oldCapacity扩容1.5倍得到newCapacity。到下次又超过了,继续扩容1.5倍.................
最终都是通过Arrays.copy方法进行复制数组,用新容量的数组装。
建议:
直接设置一定大小的容量,保证不会频繁扩容。不要使用无参构造或者把容量设置得很小,例如源码中看到的,扩容是Arrays.copy创建新数组,把旧数组的值赋值过去。频繁扩容地话,影响效率。