ArrayList源码解析
ArrayList介绍
ArrayList是我们集合框架中最常使用的数据结构之一。它实现了RandomAccess接口,支持快速随机访问。底层基于数组实现容量大小的动态变化,下面是ArrayList的类图:
-
AbstractCollection:该抽象类是Collection接口的一个基本实现,提供了部分操作集合的方法
-
AbstractList是一个抽象类,实现了List接口。提供了一部分操作集合的方法,和get(int index)等抽象方法
RandomAccess:“随机访问”接口,实现了这个接口的类是支持快速随机访问的。代码注释中还特意说明:如果实现了这个接口,for循环速度方式会优于迭代器:
/*
* <pre>
* for (int i=0, n=list.size(); i < n; i++)
* list.get(i);
* </pre>
* runs faster than this loop:
* <pre>
* for (Iterator i=list.iterator(); i.hasNext(); )
* i.next();
* </pre>
*/
public interface RandomAccess {
}
1.8 中的 ArrayList
Fields
// ArrayList的默认容量为10
private static final int DEFAULT_CAPACITY = 10;
// 供空实例使用的共享空数组实例,容量可变
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认大小时的空数组实例,new空数组时,将会赋值给空数组,容量固定
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储ArrayList元素的数组缓冲区,作为指向数组数据的指针
transient Object[] elementData;
// 存储数组的长度
private int size;
// 数组的最大长度,-8是因为需要保留一些空间存储对象头等信息
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 集合被修改的次数
protected transient int modCount = 0;
Arraylist 的构造方法
-
无参构造:由该函数可以看出,在使用无参构造创建ArrayList数组时,并不会初始化ArrayList的长度,而是在使用时(添加元素)才初始化。
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
-
有参构造1:该构造函数的入参为数组的初始长度(容量),在初始化ArrayList时,首先判断入参容量的大小,若大于0,则会初始化一个容量为 initialCapacity 大小的Object数组作为该ArrayList的初始状态存在;若入参为0,则会将常量中的EMPTY_ELEMENTDATA空数组赋值给该数组,避免创建多余的空对象数组;当入参小于零时,报异常:
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { // 初始化容量为 initialCapacity this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // 容量为0时使用常量EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
-
有参构造2:入参为“要将其元素放入此列表中的集合”,在该有参构造中,首先将elementData指向c.toArray(),然后判断当前数组长度是否为0;如果为0,则将当前ArrayList初始化为空;否则就判断当前elementData指向的类型是否为Object[],如果不是,则使用Arrays.copyOf()方法复制一块新的内存区域存储转换为Object[]类型的c数组。Arrays.copyOf()方法底层使用System.arraycopy()实现数组的复制。代码如下:
public ArrayList(Collection<? extends E> c) { // 修改elementData指向 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; } } /* * Arrays.copyOf()中的部分代码 * original:元数据 * copy:要复制到的目标数组 */ System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
常用方法的源码
add(E e):向列表的末尾添加元素
该方法的作用是将指定的元素添加到集合的末尾。在该方法中,首先判断当前集合长度是否符合要求,在ensureCapacityInternal()方法中,当集合未初始化时,会首先初始化集合长度(ArrayList的无参构造未做初始化操作),然后使用ensureExplicitCapacity()方法提升集合中数组长度。在ensureExplicitCapacity()方法中,使用了grow()方法增加数组长度。由grow()方法可见,正常情况下,ArrayList的扩容操作会将容量提升至原容量的1.5倍,超长时,依据情况将容量置为最大容量或Integer的最大值。
public boolean add(E e) {
// 判断数组容量大小
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//数组为空时,取DEFAULT_CAPACITY, minCapacity大的一方作为数组容量
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 提升数组长度
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
// 修改次数+1
modCount++;
//容量大于数组长度时,提升数组的长度
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 获取原数组长度
int oldCapacity = elementData.length;
// 新的数组长度等于旧数组长度+旧长度右移一位(即1.5倍长度)
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
// 1.5倍容量小于要增长的容量时,将容量设置为newCapacity
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 新数组长度大于数组最大容量,使用hugeCapacity()获取新容量
newCapacity = hugeCapacity(minCapacity);
// 复制
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 当容量大小超过定义的数组最大容量时,返回Integer的最大值作为数组长度
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
get(int index):获取指定位置的元素
ArrayList中的get()方法用于获取集合在指定索引位置存储的元素。
public E get(int index) {
// 检查是否越界
rangeCheck(index);
// 获得elementData index位置上的元素
return elementData(index);
}
// 检查索引位置是否有效
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 获取元素
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
set(int index, E element): 修改指定索引位置的值
ArrayList中的set()方法用于将此列表中指定位置的元素替换为指定元素,返回值为被替换掉的旧值。
public E set(int index, E element) {
// 检查索引是否越界
rangeCheck(index);
// 取出原始值
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
remove(int index):删除指定位置的元素
用于删除此列表中指定位置的元素并返回旧值,并将后面的元素向左移动。
public E remove(int index) {
rangeCheck(index);
// 修改次数+1
modCount++;
// 被删除的旧值
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
总结
从ArrayList中我们常用的增删改查方法可以看出:在向ArrayList集合中添加或删除元素时,基本都涉及到对原数组修改后的移动(其实是在内存中的复制)操作,在频繁的增删操作中,性能较低。而得益于数组结构的优越性,在获取元素方面,ArrayList拥有很快的响应速度,因此在ArrayList元素的获取和修改方面,显得效率较高。