在开始走查代码前,我们最好做一些问题的提出,然后我们再带着问题去走查代码,这样效果肯定会更好.
查看ArrayList的定义,我们发现ArrayList有如下几个关键的属性.
// 内部用来存储数据的数组
transient Object[] elementData;
// ArrayList的大小
private int size;
// 默认大小
private static final int DEFAULT_CAPACITY = 10;
以上没什么可解释的,内部维护了一个Object[]的数组,和一个int类型的大小字段.
接下来我们看构造方法是如何初始化上面这些字段的.
有参构造
public ArrayList(int initialCapacity) {
// 对入参进行判断
if (initialCapacity > 0) {
// 初始化数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 空数组 {}
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 入参小于0,抛异常
throw new IllegalArgumentException("Illegal Capacity: " +
initialCapacity);
}
}
无参构造
public ArrayList() {
// 赋值数组为空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
肯定有人会有这样的猜想:空参数构造器不应该把数组初始化成默认大小(上面提到的一个属性DEFAULT_CAPACITY = 10)的吗,空参数构造其实确实会把数组初始化成默认的大小,只不过不是在构造器,而是在第一次执行add(E e)方法的时候.下文讲到add方法时我会说明.
add(E e) 方法
public boolean add(E e) {
// 关键方法,涉及到需要的最小容量的计算,如果最小容量大于当前数组的size,还会自动扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 给数组中指定下标的元素赋值
elementData[size++] = e;
return true;
}
ensureCapacityInternal(int minCapacity) 方法
// 有两个方法,下面分别介绍
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity(elementData, minCapacity) 方法,主要确认当前Arraylist的容量大小,主要是最小容量minCapacity和默认容量之间的选择.如果是空参构造来的,那么会在这个地方将容量设置成默认容量(上面介绍空参构造器时提到过这点).
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 如果是空参构造器来的,则会进入这里,那么会返回DEFAULT_CAPACITY
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 直接返回minCapacity
return minCapacity;
}
ensureExplicitCapacity(int minCapacity) 方法,主要处理自动扩容
private void ensureExplicitCapacity(int minCapacity) {
// 这个参数大家先别管,主要是并发修改时用的,ConcurrentModificationException异常的抛出就和这个字段有关,
// 后面这个话题可能会专门开一篇来说明
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
// 如果需要的最小容量比当前数组的容量大,则进行扩容
grow(minCapacity);
}
grow(minCapacity) 方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 新容量=旧容量 + 旧容量/2
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 新容量不能小于 允许的最小容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 新容量如果大于 Integer.MAX_VALUE - 8,则对新容量做一些处理
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 拷贝旧数组到新数组,并增加新数组的容量
elementData = Arrays.copyOf(elementData, newCapacity);
}
hugeCapacity(minCapacity) 方法,当minCapacity特别大的时候的处理
private static int hugeCapacity(int minCapacity) {
// minCapacity<0,说明已经超过了int的最大值
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 返回最大的Integer 或者 最大的Integer-8
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
这里对MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8做一个简单的解释,java中创建数组最大的长度只能是Integer.MAX_VALUE - 8,比这个数字再大就会报错,注意,这个错并不是内存溢出.
get(int index) 方法,这个方法很简单
public E get(int index) {
// 检查范围,如果比size(这个size不是数组的size)大,就抛IndexOutOfBoundsException
rangeCheck(index);
// 从数组中取值
return elementData(index);
}
这里面涉及的两个方法都很简单,就不展开讲了,如果想知道,点进去自己看吧.
remove(int index) 方法
public E remove(int index) {
// 同上检查index
rangeCheck(index);
// 上面说过,先不要关注这个字段
modCount++;
// 记录旧值,用于返回
E oldValue = elementData(index);
// index后面需要移动的元素的个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 将index后的元素向前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 移动后,将size处置为null,并将size-1
elementData[--size] = null; // clear to let GC do its work
// 返回删除前的值
return oldValue;
}
remove(Object o) 方法
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;
}
fastRemove(int index) 方法
private void fastRemove(int index) {
// 先不管这个参数
modCount++;
// 同上,需要移动的参数个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 移动index后的的元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将size处的元素置空并将size-1
elementData[--size] = null; // clear to let GC do its work
}
indexOf(Object o) 方法
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
这个方法很简单,就是遍历数组,然后比较值和入参是否相等.
ArrayList大体上就上面这些常用的方法,还有一些没列出的,大体上来看,ArrayList还是比较简单的,关键的方法在add上,重点关注扩容.就先到这吧