List遍历删除元素remove()

时间:2021-2-20 作者:admin

今天碰见根据条件进行list遍历remove的问题,第一时间就是简单for循环remove,只知道这么写不行,不安全,可是为什么呢?你想过吗?下面就关于List遍历remove的问题,深挖一下!

一、几种常见的遍历方式

1、普通for循环

2、高级for循环

3、iterator和removeIf

4、stream()

5、复制

6、普通for循环 –> 倒序方式

二、源码篇

1、普通for循环出错原因

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 remove method that skips bounds checking and does not
 * return the value removed.
 */
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        //remove会导致之后的元素往前移动,而下标不改变时就会出现bug
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

我们在删除某个元素后,list的大小发生了变化,这时候你的的索引也会发生变化,这时就会导致你在遍历的时候漏掉某些元素。
比如当你删除第1个元素后,我们如果还是继续根据索引访问第2个元素时,因为删除的关系,后面的元素都往前移动了一位,所以实际访问的是第3个元素。
所以这种方式可以用在删除特定的一个元素时使用,但不适合循环删除多个元素时使用。

2、高级for循环出错原因

foreach其实是用迭代器来进行遍历的,而在遍历时直接使用arraylist的remove方法会导致什么问题呢?

可以再看一下fastremove和迭代器遍历的内部代码:

最后导致抛出上面异常的其实就是这个,简单说,调用list.remove()方法导致modCount和expectedModCount的值不一致而报异常 

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
//调用next时会调用checkForComodification方法检查 这两个字段
//而fastRemove里面只对modCount 进行了改变 导致抛出异常
public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

所以遍历时remove并不适用于foreach。

3、java8中新方法removeIf

//内部其实就是迭代器遍历
default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
}

和迭代器差不多,内部实现也是迭代器。

三、总结

1、在不考虑内存大小会不会出现OOM的时候,采取复制一个新的list的方法速度更快,适用于集合中对象不算多的时候,毕竟只需要add操作。

2、当集合中元素过多时,复制list就显得有些笨重了,采用迭代器的方式进行遍历较快一些,并且不用关注小角标的变化。

3、不考虑性能的时候使用removeIf方法,代码简洁明了。

4、当要针对角标进行元素的remove时,使用倒序遍历的方式最为妥当。

 

上一篇:Java基础知识总结(绝对经典)

下一篇:Java面试题总结(附答案)

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。