ArrayList源码解析:增删改查
ArrayList源码解析
arrayList就是动态数组,可以动态的添加和减少元组,实现了ICollection和Ilist接口以及灵活的设置数组的大小。
4. 增删改查
4.1 添加元素
通过先前的字段的分析,可以得出ArrayList集合的底层是通过数组实现的,那么向ArrayList中添加元素就是在向数组中赋值。但是数组是固定大小的。需要实现任意添加元素就必须涉及到数组的扩容。
下面是对于add(E e)函数的分析:对于该函数总体的框架就是先确定数组的大小是否足够添加元素,然后添加元素,并返回成功标志。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 在添加到数组之前需要确定数组的大小
elementData[size++] = e;
return true;
}
再来看看ensureCapacityInternal(size + 1)这个操作。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
这里套了两层娃,我们从里到外一层一层拆开看。首先在calculateCapacity(elementData, minCapacity)传入了数组和当前集合的大小。并将返回值又传入ensureExplicitCapacity()方法。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果数组为空,表示当前数组的大小是0,则从DEFAULT_CAPACITY和minCapacity中间选择一个最大的值返回
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;// 不为空则返回size+1
}
从calculateCapacity返回的值则传递给了ensureExplicitCapacity
//这个属性表示结构修改的次数,也可以说是数组扩容的次数
protected transient int modCount = 0;
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 判断该位置是否溢出
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这个函数判断了minCapacity是否大于当前内部数组的长度,大于则调用grow方法对内部数组扩容。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//得到原始数组的长度
int newCapacity = oldCapacity + (oldCapacity >> 1);//新数组的长度就是原始数组的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;//当新数组长度任然比minCapacity小,则为保证最小长度,新数组为minCapacity
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);//当新数组长度比MAX_ARRAY_SIZE大的时候,调用hugeCapacity(minCapacity)处理大数组的情况
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//调用Arrays.copyOf将原数组的元素拷贝到一共大小为newCapacity的新数组,然后将新数组的引用传递给了elementData
}
当当新数组长度比MAX_ARRAY_SIZE大的时候,调用hugeCapacity(minCapacity)处理大数组,虚拟机对于数组的长度有限制。尝试分配更大的数组可能会导致 OutOfMemoryError:请求的数组大小超出 VM 限制
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 排除错误情况
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
}
这里给出了两种选择Integer.MAX_VALUE :MAX_ARRAY_SIZE;
对于ArrayList集合添加元素,总结如下:
- 当通过ArraysList()构造一个空集合,初始长度为0的,第一次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。
- 第二次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度,所以直接添加元素到数组的第二个位置,不用扩容。
- 第11次添加元素,此时size+1=11,而数组长度是10,这时候创建一个长度为10+10*0.5的数组,然后将元素数组元素引用拷贝到新数组,并将元素添加到对应位置。
- 数组的大小最多为Inter.MAX_VALUE,此时数组将不再扩容,超出界限将抛出outOfMemoryEorror异常。
4.2 删除元素
1. 根据索引位置删除元素
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;
}
首先通过rangeCheck()方法来确定index是否在正确的范围。如果超过size则抛出异常。
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
在index确认无误之后,记录此列表在结构上修改的次数。
protected transient int modCount = 0;//表示此列表在结构上被修改的次数
通过elementData方法取出内部数组对应位置上的元素。
E elementData(int index) {
return (E) elementData[index];
}
最后对数组自身进行拷贝,并将数组的最后一个元素变成null。
2. 直接删除指定的元素
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;
}
remove(Object o)可以将第一次出现的o元素删除,该方法判断元素时,将是否为空的情况分开讨论,当为空的时候,可以通过elementData[index] == null来查找相应的元素,当不为空的时候通过equals(elementData[index])来查找相应的元素。
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
}
找到第一个指定的元素后,调用了fastRemove方法,该方法与remove(int index)的区别是是否返回删除的元素。本质都还是自身拷贝。
4.3 修改元素
这个方法没有什么好写的,基本套路同前面一致。
- 检查索引是否合法。
- 获取索引指向的值。
- 进行修改或者删除。
- 返回旧值。
public E set(int index, E element) {
rangeCheck(index);//与在删除元素的方法中调用的方法相同,用于确认索引是否合法。
E oldValue = elementData(index);//与在删除元素的方法中调用的方法相同,用于获取指定索引的元素
elementData[index] = element;//直接在内部数组进行修改
return oldValue;
}
4.4 查找元素
1. 根据索引查找元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
2. 根据元素查找索引
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;
}
与修改方法类似,只不过将返回值改成了返回索引罢了。
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
lastIndexOf(Object o)实现返回最后一个出现的元素的下标,则是使用了从后往前遍历的方法。倒着找。