ArrayList的删除姿势你都知道了吗

简介: ArrayList的删除姿势你都知道了吗

引言

前几天有个读者由于看了《ArrayList哪种遍历效率最好,你真的弄明白了吗?》问了个问题普通for循环ArrayList为什么不能删除连续重复的两个元素?其实这个描述是不正确的。正确的应该是普通for循环正序删除,不能删除连续的元素所以就产生了这个文章。
在这里插入图片描述

ArrayList删除数据的方式

我们先看下ArrayList总共有几种删除元素的方法吧。

package com.workit.demo.array;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;

/**
 * @author:
 * @Date: 2020/6/9
 * @Description:
 */
public class ArrayListDelete {

    public static void main(String[] args) {

        Predicate<String> predicate = p -> p.equals("a") || p.equals("b");

        // 可以正常删除结果正确
        deleteByIterator(getList(), predicate);

        // 可以正常删除结果正确
        deleteByReverseOrder(getList(), predicate);

        // 可以删除 结果不正确
        deleteByOrder(getList(), predicate);

        // 不能删除 报错java.util.ConcurrentModificationException
        deleteByArrayList(getList(), predicate);

        // 不能删除 报错java.util.ConcurrentModificationException
        deleteByForeach(getList(), predicate);

        //不能删除 报错 java.util.ConcurrentModificationException
        deleteByEnhancedForLoop(getList(), predicate);
        // 正常删除数据        
        deleteAll(getList(), predicate);

    }

    public static  List<String> getList() {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        return list;
    }



    /**
     * 普通for循环倒序删除
     * 可以正常删除  结果正确
     * @param list
     * @param predicate
     */
    public static void deleteByReverseOrder(List<String> list, Predicate<String> predicate) {
        for (int i = list.size() - 1; i >= 0; i--) {
            if (predicate.test(list.get(i))) {
                list.remove(list.get(i));
            }
        }
        System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());
    }

    /**
     * 普通for循环正序删除
     *可以删除  结果不正确
     * @param list
     * @param predicate
     */

    public static void deleteByOrder(List<String> list, Predicate<String> predicate) {
        for (int i = 0; i < list.size(); i++) {
            if (predicate.test(list.get(i))) {
                list.remove(list.get(i));
            }
        }
        System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());
    }


    /**
     * 迭代器循环,使用ArrayList的remove()方法删除
     * 可以删除  结果不正确
     * @param list
     * @param predicate
     */
    public static void deleteByArrayList(List<String> list, Predicate<String> predicate) {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            if (predicate.test(iterator.next())) {
                list.remove(iterator.next());
            }
        }
        System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());

    }

    /**
     * 迭代器循环,使用迭代器的remove()方法删除
     * 可以正常删除结果正确
     * @param list
     * @param predicate
     */
    public static void deleteByIterator(List<String> list, Predicate<String> predicate) {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            if (predicate.test(iterator.next())) {
                iterator.remove();
            }
        }
        System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());

    }

    /**
     * java8 forEach方法删除
     *不能删除 报错 java.util.ConcurrentModificationException
     * @param list
     * @param predicate
     */
    public static void deleteByForeach(List<String> list, Predicate<String> predicate) {
        list.forEach(p -> {
            if (predicate.test(p)) {
                list.remove(p);
            }
        });
        System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());

    }
    /**
     * 增强版for循环删除
     *不能删除 报错 java.util.ConcurrentModificationException
     * @param list
     * @param predicate
     */
    public static void deleteByEnhancedForLoop(List<String> list, Predicate<String> predicate) {
        for (String string : list) {
            if (predicate.test(string)) {
                list.remove(string);
            }
        }
    }

}
 /**
     * 调用批量删除方法
     * @param list
     * @param predicate
     */
    public static void deleteAll(List<String> list, Predicate<String> predicate) {
        List<String> removeList = new ArrayList<>();
        for (String string : list) {
            if (predicate.test(string)) {
                removeList.add(string);
            }
        }
        list.removeAll(removeList);
        System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName()+":"+list.toString());

    }

下面我们来分析下为什么这些方法为什么有的可以正确删除元素,有的不可以。引用大佬们经常说的一句话源码之下无秘密那我们就把源码搞起来吧。

增强版for循环删除 && 迭代器循环使用ArrayList.remove()方法删除

  • 增强版for循环删除deleteByEnhancedForLoop)、迭代器循环,使用ArrayList的remove()方法删除deleteByArrayList)这两种姿势都会抛出java.util.ConcurrentModificationException他们本质都是迭代器循环,每次循环都会checkForComodification这个方法检查modCountexpectedModCount的值。
    @SuppressWarnings("unchecked")
        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];
        }
         final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

List的删除方法每次删除之后modCount都会进行加1操作,expectedModCount值不变还是原来的。

 private void fastRemove(int index) {
        modCount++; //modCount`都会进行加1操作
        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
    }

所以上面两个方法都会抛出ConcurrentModificationException异常。

java8 forEach方法删除(抛出异常)

  • java8 forEach方法删除deleteByForeach)为什么也会抛**ConcurrentModificationException异常呢?答案还是在源码里面。

同上面一样删除一个元素后modCount 进行了加1expectedModCount 没有变化。

 public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) { // 是不是又是这个判断
            throw new ConcurrentModificationException();
        }

正序删除不能删除连续元素的原因

  • 可以删除但是结果不正确的方法for循环正序删除deleteByOrder

先来张图吧,看图更直观。
在这里插入图片描述
数组删除元素后每次都需要移动。第一次删除(i=0)后b的下标就为0了,然后第二次(i=1)进行删除的时候是不是就成功的把b给遗漏了。(倒序循环删除就可以避免这种情况)那如果我们非要使用正序循环删除数据那有什么解决办法吗?办法是有的只要在删除后面把i的值进行修正下。代码如下:

 for (int i = 0; i < list.size(); i++) {
            if (predicate.test(list.get(i))) {
                list.remove(list.get(i));
                // 新增这个修正i的值
                i--;
            }
        }

是不是又get了一个骚操作。
在这里插入图片描述

使用迭代器的remove()方法删除(推荐做法)

迭代器循环,使用迭代器的remove()方法删除deleteByIterator)这个比较简单我们直接看迭代器的删除
关键代码就一行 expectedModCount = modCount

  public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount; //调用ArrayList的删除方法后多了这么一句话。
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

这种方法也是《阿里开发手册》(需要的可以公众号回复:泰山)推荐的。
在这里插入图片描述

总结

上面列举了一系列的删除方法,稍不小心使用不当就踩坑里面了。这么多我也记不住啊?最好的方法就是不要边循环边删除数据。如果非要删除咧?个人建议可以使用批量删除方法(本人屡试不爽)或者迭代器的remove()方法。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。
目录
相关文章
|
数据处理 Windows
Inertial Explorer v8.8航测pos解算软件安装教程
Inertial Explorer v8.8航测pos解算软件安装教程
2696 1
|
关系型数据库 MySQL Windows
mysql彻底卸载干净的5个步骤,超多图超详细保姆级教程最新教程新手小白轻松上手
mysql彻底卸载干净的5个步骤,超多图超详细保姆级教程最新教程新手小白轻松上手
25906 2
|
9月前
|
编解码 安全 Android开发
如何修复 Android 和 Windows 不支持视频编解码器的问题?
视频播放时遇到“编解码器不支持”错误(如0xc00d36c4或0xc00d5212)是常见问题,即使文件格式为MP4或MKV。编解码器是编码和解码数据的工具,不同设备和版本支持不同的编解码器。解决方法包括:1) 安装所需编解码器,如K-Lite Codec Pack;2) 使用自带编解码器的第三方播放器,如VLC、KMPlayer等。这些方法能帮助你顺利播放视频。
|
存储 安全 测试技术
数组越界:深入理解、危害与防范
数组越界:深入理解、危害与防范
2579 18
|
关系型数据库 MySQL
MySQL 分库分表实战
MySQL 分库分表实战
280 0
|
11月前
|
存储 数据可视化 网络协议
什么是ELK栈?如何与Spring Boot一起使用?
什么是ELK栈?如何与Spring Boot一起使用?
472 0
|
缓存 NoSQL Java
Spring Cache之本地缓存注解@Cacheable,@CachePut,@CacheEvict使用
SpringCache不支持灵活的缓存时间和集群,适合数据量小的单机服务或对一致性要求不高的场景。`@EnableCaching`启用缓存。`@Cacheable`用于缓存方法返回值,`value`指定缓存名称,`key`定义缓存键,可按SpEL编写,`unless`决定是否不缓存空值。当在类上使用时,类内所有方法都支持缓存。`@CachePut`每次执行方法后都会更新缓存,而`@CacheEvict`用于清除缓存,支持按键清除或全部清除。Spring Cache结合Redis可支持集群环境。
1102 6
|
SQL 存储 数据库
实验4:SQL视图操作技巧与方法
在数据库管理系统中,视图(View)是一种虚拟表,它基于SQL查询的结果集创建,并不实际存储数据
|
存储 分布式计算 监控
Spark Standalone模式是一种集群部署方式
【6月更文挑战第17天】Spark Standalone模式是一种集群部署方式
234 7
|
前端开发 iOS开发
input框设置placeholder属性在iOS中显示不完整
input框设置placeholder属性在iOS中显示不完整
198 1