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集合添加元素,总结如下:

  1. 当通过ArraysList()构造一个空集合,初始长度为0的,第一次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。
  2. 第二次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度,所以直接添加元素到数组的第二个位置,不用扩容。
  3. 第11次添加元素,此时size+1=11,而数组长度是10,这时候创建一个长度为10+10*0.5的数组,然后将元素数组元素引用拷贝到新数组,并将元素添加到对应位置。
  4. 数组的大小最多为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 修改元素

这个方法没有什么好写的,基本套路同前面一致。

  1. 检查索引是否合法。
  2. 获取索引指向的值。
  3. 进行修改或者删除。
  4. 返回旧值。
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)实现返回最后一个出现的元素的下标,则是使用了从后往前遍历的方法。倒着找。

posted @ 2021-09-26 14:36  锤子布  阅读(169)  评论(0编辑  收藏  举报