ArrayList 是我们经常使用到的数据结构,于是决定认真阅读以下它的源码,看看是怎么实现的。
首先,从构造方法可以看出,ArrayList 其实就是一个Object数组
private static final Object[] EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
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);
}
}
add操作
大致流程是:先判断数组容量是否够用,是否需要扩容,在数组尾端直接增加一个元素。
这里我刚开始有个疑问,size 和 elementData.length 的区别,乍一看我以为是一样的,后来仔细一看才发现,size 是elementData里含有对象的个数也就是列表的大小,elementData.length 是elementData 的容量
public boolean add(E e) {
ensureCapacityInternal(size + 1); //确定内存是否够用
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);// 如果是初始化空数组,就返回比较大的值
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//修改次数加一
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);//扩容
}
private void grow(int minCapacity) {
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);
elementData = Arrays.copyOf(elementData, newCapacity);
}
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 操作
大致流程是,先检查索引是否在范围内,在的话直接返回,否则会抛异常,告诉你当前列表的大小
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
指定位置增加元素
首先检查索引是否在范围内,是否需要扩容,再用System.arraycopy进行复制,给index位置元素赋值
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
移除指定索引元素
首先检查索引是否在范围内, 计算需要复制的长度,复制数组从index + 1开始到结尾到原数组 从index 到 size - 1的位置, 最后这里一定要将尾端的元素设为 null,防止内存泄漏
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;
}
移除指定对象
大致流程:遍历数据,找到与指定元素相等的元素,进行移除,移除操作就是和上面移除操作逻辑相同。这里可以看出一旦找到相同元素,移除后就会返回,只删除第一个匹配到的元素
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
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
}
清除操作
大致流程:循环遍历数据,设置每个元素为null
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
至此 ArrayList 的基本操作都已看完,ArrayList 底层就是一个数组,所以具备数组的特性,可以很快根据索引定位到元素的值,尾部追加元素的操作比在指定位置增加元素更低效的原因是多了一个赋值数组的操作。这里System.arraycopy 有待进一步深究