Java之ArrayList源码分析(第三篇:删除元素)

(注意:本文基于JDK1.8)

删除元素的4个方法

清空元素的clear()方法

remove(int)方法分析

    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;
    }

删除指定位置index(下标)的元素并返回旧的元素,传入参数index表示下标值

1、检查下标值是否符合要求

通过rangeCheck()方法完成下标值index的检查工作,检查下标值是否满足范围要求,如果传入的下标值index超过或者等于元素总数size,将抛出一个IndexOutOfBoundsException异常对象,具体的检查过程,下文会提到rangeCheck()方法的源码分析

2、fail-fast机制,防止多线程下使用ArrayList的办法

将实例变量modCount加1,表示已经产生了修改元素的行为,modCount并不在ArrayList中定义,我向上查找,发现它在基类AbstractList中定义

3、获取当前下标index中的已经保存的元素

从ArrayList对象持有的底层数组对象elementData中的下标index处取出元素对象,并赋值给局部变量oldValue持有

4、计算需要挪动的元素数量

现在需要删除一个元素,空余的位置,需要后面的元素向前移动占满,先计算下需要挪动几个元素,使用公式size - index - 1即可

举个例子:假设目前ArrayList一共保存了5个元素,此时size==5,你需要删除下标0的元素,我们用公式计算一下需要挪动几个元素(size - index - 1)。5 - 0 - 1 = 4,共计4个元素要向前“挪动”,其实是下标0之后的4个元素,依次向前复制一位,比如开始的数组是【"8","9","3","5","6"】,现在删除下标0的元素,然后下标0后面的4个元素会向前复制一位,则数组变为【"9","3","5","6","6"】,看到了吧?数组的最后一位此时还是存在的,只是第一位的元素被覆盖了……

5、向前挪动所有元素

使用System的arraycopy()方法完成“挪动元素”

第一个参数elementData表示需要挪动的源数组对象

第二个参数index+1表示从源数组对象的哪个下标处开始

第三个参数表示要拷贝到的目标数组对象

第四个参数表示从目标数组对象的哪个下标处开始拷贝

第五个参数表示复制的元素总数,挪动数量numMoved即表示需要复制的元素总数

6、将元素总数size减1

7、将无用的元素赋值为null,便于GC回收

因为所有index后面的元素都向前挪动了一位(向前复制),此时size处下标仍然保存的是未挪动前的元素对象,只有直接赋值为null,日后此对象才能被GC回收掉(不然就会造成数组多个下标指向同一个对象,造成对象无法被回收)

8、向调用者返回结果

返回旧的元素对象

rangeCheck(int)方法分析

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

用于检查下标值index是否符合范围的方法

1、判断传入的下标值index大于或者等于元素总数size

如果不符合要求,将执行下面的步骤2和步骤3

2、生成异常信息字符串

先是调用了一个outOfBoundsMsg()方法,outOfBoundsMsg()方法接受了下标值index,该方法返回的字符串信息会在控制台展示,在第二篇文章中已经分析了outOfBoundsMsg()方法的实现,这里不再解释

3、抛出一个IndexOutOfBoundsException异常对象

remove(Object)方法分析

    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;
    }

用于将相等的元素对象删除的方法,传入的参数表示元素对象

因为ArrayList支持添加null作为元素,所以作者将null元素与非null元素的删除逻辑分开处理,我们分别学习一下

一、传入的元素为null

当传入的元素为null时,则会从数组对象elementData的第1个元素开始遍历,一直遍历到元素总数size-1的下标处,每1轮的迭代会判断elementData对应下标的元素值是否为null,此处使用==作为元素相等的判断依据,因为null无法使用equals()方法比较对象是否相等

1、当从ArrayList对象持有的elementData找到一个元素为null时,调用fastRemove()方法进行删除元素,并结束遍历,整个remove(Object)方法会返回true,表示找到相等的元素,并删除元素成功

2、如果遍历结束,没有在elementData中找到任何的null元素,remote(Object)方法会返回false,表示删除元素失败

二、传入的元素为非null时

传入的元素为非null,同样从底层数组对象elementData的第一个元素开始遍历,遍历过程中每一轮都会调用传入的元素Object对象的equals()方法与elementData对应下标中的元素对象进行比较,此处使用Object的equals()方法比较元素对象的相等性,equals()方法的返回结果表示相等性,true表示两个对象相等、false表示两个对象不相等

1、当在elementData中找到一个匹配的元素(此时equals()方法返回true),执行fastRemove(int)方法进行删除元素,之后遍历结束,整个remove()方法返回true,表示删除元素成功

2、当遍历结束,没有找到匹配相等的元素,整个remote(Object)方法返回false,表示删除元素失败

fastRemove(int)方法分析

    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
    }

删除ArrayList对象持有的elementData数组对象中元素的方法,学习一下它的源码

说明:无论删除null元素、还是删除Object元素,都会调用该方法进行删除

1、fail-fast机制,防止多线程下使用ArrayList

将表示修改次数的实例变量modCount加1

2、计算需要向前挪动的元素数量

计算公式为size - index - 1,结果保存在局部变量numMoved中

3、当需要挪动的元素大于0,执行数组元素复制,即数组元素挪动

同样使用System的静态方法arraycopy()完成元素的复制(挪动),删除操作会执行向前复制元素

4、元素总数size减去1

5、将不再使用的下标赋值为null,便于GC未来回收指向的对象

size值作为下标,将其赋值为null

removeAll(Collection)方法分析

    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

用于删除指定集合中的多个元素,传入一个Collection对象,表示待删除的元素集合

1、检查传入的Collection必须不能为null

调用了Objects下的方法来判断是否为null

2、调用一个batchRemove()方法完成批量删除工作

调用batchRemove()方法,同时传入一个Collection对象与一个false值

3、返回batchRemove()方法的执行结果给调用方

batchRemove()方法分析

    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

用于删除集合中的所有元素

1、获取ArrayList对象持有的底层数组对象elementData

首先获取当前ArrayList对象持有的底层数组对象elementData,并赋值给一个同名的局部变量elementData持有

2、定义两个局部变量

一个局部变量r表示读取下标(遍历时elementData数组时的,读取下标)

一个局部变量w表示写入下标

3、定义一个标志位

局部变量modified,初始值为false,表示什么呢?后面看看

4、检查现有ArrayList对象持有的每一个元素是否与传入的Collection对象的任意一个元素匹配,如果不匹配,说明是要保留的元素对象,此时将要保留的元素会继续存放在底层数组对象elementData中下标w处,并且下标w之后会加1,最终w坐标及之前的所有元素均为需要存入的元素,这里需要画一张图

5、针对c.ontains()方法抛出异常的情况做处理

finally代码块对c.contains可能抛出异常的情况也做了处理,如果c.contains抛出了异常,则r一定不等于size,此时将r下标后面的元素一个一个的复制到以w下标开始的数组中

6、清除无用元素占用的数组位置

将保留所有元素的后面位置,全部赋值为null,是从局部变量w开始的下标算起,size减去局部变量w的值就是要被修改的数量,此时也会对modCount做了更新,后面又更新了最新的元素总数,w的值目前就是新的元素总数,所以最后又赋值给size

7、将标志位更为true

标记到modified变量中,表示删除结果

8、返回删除结果

将modified的boolean值返回给调用方

clear()方法分析

    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

用于清空ArrayList所有元素的方法

1、fail-fast机制,防止多线程下使用ArrayList的办法

首先将modCount增加1个,其它位置对modCount会做判断

2、将elementData数组对象中的所有元素赋值为null

将ArrayList对象持有的底层数组elementData对象中的持有的所有对象,全部赋值为null,剩下的工作就交给GC了

3、更新元素总数

元素总数size赋值为0

总结

1、modCount值用于fail-fast机制(防止多线程下使用ArrayList),添加单个元素或多个元素、删除单个元素或多个元素、清空所有元素时,均对modCount值做了+1,表示修改次数加1

2、中间删除元素,也是O(n)的时间复杂度,因为删除元素后,需要将后面的元素,一个一个的向前复制元素(表示挪动元素)

3、感叹作者想的好周全,removeIf()方法将单独分析……

4、System的静态方法arraycopy()继续作为数组元素向前"挪动"的功能,这个native方法值得学习

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值