ArrayList是JAVA的一种泛型容器,它实际上是一个用数组实现的链表,今天解析一下ArrayList的源码,并写个简单的实现。
ArrayList的依赖关系
首先我们来看ArrayList的依赖继承关系:
通过ArrayList继承实现的接口说明了该类具有以下特性:
- ArrayList具有链表的特性
- Collection接口表示ArrayList是一个数据集合,但是这个数据集合没有位置和顺序关系,因此ArrayList实现了类似isEmpty,contains的方法
- RandomAccess接口说明了ArrayList支持随机访问,这是因为底层是用数组实现,天然支持随机访问
- Iterable接口表示ArrayList支持迭代访问,可以使用foreach语法进行数据的遍历
- Serializable接口表示ArrayList的数据支持序列化
- Cloneable接口表示ArrayList支持对象的克隆
ArrayList原理解析
ArrayList是通过数组实现,该类内部定义了一个Object数组用于存放数据,由于存放的数据类型是一个泛型,因此只能将数组定义为Object[]类型,这是因为Object类是所有类的父类,因此不管什么类型的对象都能赋值给Object的变量。
transient Object[] elementData;
public ArrayList(int initialCapacity)
public ArrayList()
public ArrayList(Collection<? extends E> c)
ArrayList的构造函数有三种,如果没有给定初始的数组大小,那么将创建一个空的数组;如果给定数组初始大小,那么将创建对应大小的数组;也支持给定一个Collection对象,那么该对象内的数据将赋值到elementData数组内。
ArrayList数据的添加
ArrayList调用add方法添加数据时,会先检查一下当前数组大小是否具有足够的空间。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
当数组空间不足时,最终会跳转到grow方法进行数组扩容,该方法是计算原空间大小*1.5与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);
}
ArrayList数据的删除
ArrayList的底层实现是数组,数据连续排放,所以在删除数据时,效率较低,需要进行数据的挪动。
public E remove(int index) {
rangeCheck(index);
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;
}
假设数组大小是size,要删除的数据下标是index,那么下标大于index的数据都要向左挪动一位,并将原来的最后一位置为空。System.arraycopy执行的功能实际上就是进行数据的平移。
ArrayList转数组
由于Array的底层实现是一个数组,因此,ArrayList可以将维护的数组返回,但是由于这是一个泛型容器,如果直接将底层的Object[]数组强制转化为T[]返回,编译器会报错,java不允许父类对象强转为子类变量。
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
toArray方法实现对具体类型数组的转化,该方法有一个T[] a的参数,如果a的大小足够存放所有数据,那么直接进行数据的复制并返回结果。
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
如果a数组的大小不够大,那么需要创建一个新的T[]数组进行数据的复制。Array.newInstance(newType.getComponentType(), newLength)是利用了反射,通过给定的对象类型,创建一个指定大小,指定对象类型的数组,因为创建的数组本身就是T[]类型的,因此这里的强转是没有问题的。