ArrayList是java语言中使用最广泛的一种集合,它的底层是基于数据实现的。今天我就来带大家彻底搞懂ArrayList的一些技术细节。
我们都知道,ArrayList底层是基于数组来实现。数组这种数据结构的最大优点,就是支持随机查询,因为在内存中,数组是一块连续的存储空间,只要知道数组的起始地址,一级数组中某个元素的下标,我们很容易就能计算出这个元素的地址,从而查询出它的结果。但是ArrayList和原生的数组最大的不同就是,原生的数组一旦定义了,它的容量不能改变,但是ArrayList,我们知道,可以一直向其中添加元素。
这就要要探讨ArrayList底层的源码。
ArrayList类中有如下几个重要的参数:
1.默认的初始容量
private static final int DEFAULT_CAPACITY = 10;
2.空数组用于初始化一个长度为0的数组
private static final Object[] EMPTY_ELEMENTDATA = {};
3.空数组,用于判断何时使用默认的初始容量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
具体这个数组的功能我们接下来介绍。
4.用于存放具体元素的数组
transient Object[] elementData;
5.ArrayList中实际存放的元素个数,注意不是ArrayList的容量
private int size;
ArrayList的构造函数
1.默认的构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
如果我们初始化一个ArrayList的时候不指定它的容量,默认第一次将它初始化为一个空数组。
2.带参数的构造函数
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);
}
}
如果我们初始化Arraylist的时候,指定它的容量,则会创建一个指定容量的数组。
3.用于将其他集合转换成数组的构造函数
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;
}
}
这种方法常用于将其他集合类转换成数组,例如Stack,LinkedList等等,这里不做赘述。
下面我将分两种情况讲述ArrayList的初始化,以及向其中添加元素的过程。
第一种情况,初始化的时候不指定容量,
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
首先 ensureCapacityInternal(size + 1); 用于判断向数组中添加元素,是否超过数组的容量。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
当我们第一次向数组中添加元素的时候 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 条件满足,于是这时会将
minCapacity会取到默认的初始容量10,然后进入到ensureExplicitCapacity(minCapacity) 方法判断是否此时超过数组的容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
此时elementData数组长度为0,所以会进入grow(minCapacity) 方法进行扩容;
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 = 0,所以第一次会将数组容量扩充到默认的10,后续会在之前的基础上扩容50%。
oldCapacity >> 1 表示右移一位,相当于除以2。
这是不指定参数的ArrayList的初始化的流程。
如果初始化的时候指定数组的大小,则此时没有第一次将数组从0扩容到初始容量的过程。因为第一次初始化的时候
this.elementData = new Object[initialCapacity];
已经将elementData数组初始化为一个。