ArrayList 源码机制解读 Java
ArrayList简介
集合中ArrayList是有序、可重复的(有序指的是元素插入的顺序是有序,可重复是指元素可以重复)
底层原理:
是通过数组实现的。但是它有自动的扩容机制。
特点:
-
优点:
搜索速度快,因为可以通过下标直接定位到目标对象。 末端插入删除速度最快
-
缺点:
对内存的要求高,因为添加删除需要移动元素位置。
底层源码分析
先了解一下,ArrayList的属性以及它的构造器:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// DEFAULT_CAPACITY是默认的缺省数组长度,为10.
// 先记住,如果我们new了一个空的ArrayList(没有传递参数), 那么它的默认长度就是10;
private static final int DEFAULT_CAPACITY = 10;
//声明的空数组,这个是有参构造器使用的
private static final Object[] EMPTY_ELEMENTDATA = {};
//声明的空数组,以供无参构造器使用
private static final elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存数据的缓冲数组,可以理解为就是我们在操作的数组(错了请大神指出)
transient Object[] elementData;
//数组的长度,也就是元素个数
private int size;
}
构造器
//有参构造器
public ArrayList(int initialCapacity) { //传入参数
if (initialCapacity > 0) { //如果参数大于0,新建一个Object类型的数组并用指定的参数作为数组长度,之后赋值给属性中声明好的缓冲区数组elementData.
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//如果传入的参数是0,就是建立一个长度为0的数组
this.elementData = EMPTY_ELEMENTDATA; //使用EMPTY_ELEMENTDATA
} else { //如果传入的是负数,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//无参构造器
public ArrayList() { //将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给缓存数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//将集合作为参数传入
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
了解了,属性和构造器后,我们来了解ArrayList的扩容原理。
首先,我们创建新的ArrayListList list = new ArrayList();
注意:此时默认调用的是无参构造器;
也就是说用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给 elementData;
接下来让我们添加数据,list.add("yy");
这个时候进入add方法;
//add方法,添加参数中的对象,到数组中
public boolean add(E e) {
ensureCapacityInternal(size + 1); //size = elementData.length
elementData[size++] = e;
return true;
}
这里我们看到add方法首先调用了ensureCapacityInternal();
方法, 这个方法的作用是,要确保数组的容量要比当前的数组元素多一个,保证要添加的数据可以被添加到数组中。
让我们在看一下ensureCapacityInternal();
的源代码:它的内部用调用的ensureExplicitCapacity()方法,
而在这个方法内部用调用了calculateCapacity()方法;
可以说这是一个连环压栈的方法;
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
-
接着我们继续压栈,看看连环压栈的两个方法:首先调用的时
calculateCapacity();方法
我们将minCapacity的当前值也就是1传入, -
注意,在初次调用add方法时,
ensureCapacityInternal(size + 1);
这个时候minCapacity = 1;因为size在此时为0,elementData.length在此时数组还是空的;判断,当前的elementDate == DEFAULTCAPACITY_EMPTY_ELEMENTDATA, 这里其实这句代码的意思就是,数组是不是刚刚初始化。 如果是返回 Math.max(DEFAULT_CAPACITY, minCapacity); 中较大的一个数。DEFAULT_CAPACITY= 10; 而minCapacit = 1; 所以返回10; -
随后将10作为参数传入到
ensureExplicitCapacity();
中,接着进入判断,minCapacity - elementData.length > 0
,如果结果为true数组将要扩容,调用grow方法(), 当前minCapacit =1;elementData.length =0;返回true,这是我们就要进行数组的扩容;
//计算数组的容量,注意在这里,因为时第一次掉调用minCpacity为1
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断数组是不是刚刚初始化,如果是返回10,不是返回size+1
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity); //返回10
}
return minCapacity;
}
//方法:判断是否要进行扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //记录数组的修改次数
//如果当前需要的最小容量大于数组长度,扩容
if (minCapacity - elementData.length > 0) //minCapacity = 10
grow(minCapacity);
}
因为,当前数组要扩容,让我们看看扩容源码:
扩容,我们将当前的minCapacity作为参数传入,此时为10;
- oldCapacity 为当前的数组长度
- newCapacity是我们要扩容后的长度,注意这里用的是右移1位+oldCapacity,其实就是扩容为1.5倍. 这里用位操作符是因为速度更快;
接下来,判读新的容量是否小于当前需要的最小的容量。如果是的话,将新的容量设置为当前所需最小容量,也就是minCapacity的当前值10,所以这也就是无参构造器new ArrayList时,默认初始容量为10的原因;
//数组扩容
private void grow(int minCapacity) { //初次minCpacity为1
int oldCapacity = elementData.length; //当前的数组长度
int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容为原来的1.5倍数
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);
}