阿里面试官问我知道哪几种ArrayList的删除姿势?

简介: ArrayList的删除姿势你都知道了吗?我一下给面试官写了5种?

引言

前几天有个读者由于看了《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()方法。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注(微信号是Java金融)
目录
相关文章
|
4月前
|
自然语言处理 前端开发
阿里面试官:如何给所有的async函数添加try/catch?
阿里面试官:如何给所有的async函数添加try/catch?
|
4月前
|
JavaScript 前端开发 架构师
阿里前端常考vue面试题汇总(二)
阿里前端常考vue面试题汇总(二)
110 0
|
19天前
|
Java
[Java 面试题] ArrayList篇
[Java 面试题] ArrayList篇
|
2月前
|
存储 缓存 Java
什么!?实战项目竟然撞到阿里面试的原题!???关于MyBatis Plus的缓存机制
什么!?实战项目竟然撞到阿里面试的原题!???关于MyBatis Plus的缓存机制
|
2月前
|
存储 算法
【数据结构与算法】【腾讯阿里链表面试题】算法题--链表易懂版讲解
【数据结构与算法】【腾讯阿里链表面试题】算法题--链表易懂版讲解
|
3月前
|
存储 NoSQL Java
阿里面试官问我Redis怎么实现分布式锁
分布式锁一般有三种实现方式:
52 0
|
3月前
|
缓存 监控 架构师
阿里面试:Java开发中,应如何避免OOM
在Java开发中,OutOfMemoryError(OOM)错误一直是令开发者头疼的问题,也是Java面试中出现核心频率很高的问题。 那么我们究竟怎么样才能够有效正确的管理内存,日常开发中究竟要注意哪些核心技巧来避免OOM错误。 本文将带大家一起学习10个避免OOM的实用小技巧,让大家在工作中能够有的放矢,避免OOM错误的飞来横祸。
55 1
|
4月前
|
算法 Java 程序员
阿里P8大佬终于把春招面试必备的神级Java面试手册给开源了!
先说说Java Java 作为国人编程开发语言中的 NO.1,已经占比半壁江山,选择入行做 IT 做编程开发的人,基本都把它作为首选语言,进大厂拿高薪也是大多数小伙伴们的梦想。 以前Java 岗位人才的空缺,而需求量又大,所以这种人才供不应求的现状,就是 Java 工程师的薪资待遇相对优厚的原因所在。 但是随着这个从事行业的人数逐渐增多,行业竞争也越来越大,招聘的企业和程序员们都想招聘到自己需要的人才/找到自己理想的岗位,国内大厂尤其是阿里招聘Java岗位居多,导致现在 Java 面试越来越难,内卷早就是大势所趋,万物皆可卷,卷的我们都见怪不怪了。 那么,阿里Java面试难度大吗?
|
4月前
|
NoSQL Java 关系型数据库
阿里技术三面:P7想靠资历打败我,却惨败于这800页面试热题下
阿里巴巴,这个中国互联网行业中能排上前三的企业,面试是非常讲究的。通常都是三面技术面+HR面,可是多少心怀阿里梦的工作者惨败三面之中,连HR面都没见着就败了。那如何通过技术三面呢?我来介绍介绍(这里是指我技术三面的经验)
|
4月前
|
算法 Java 关系型数据库
在家“闭关”,阿里竟发来视频面试,4面顺利拿下offer
关于个人呢,我是一个普通的双非本科生,在校成绩不错,各方面的表现自我感觉也比较突出,今年大四即将毕业,对自己进入大厂工作是很有信心的,我的方向是Java,也知道现在Java的竞争比较激烈,大厂比较难进,但我丝毫不胆怯。当然,我还是很走“狗屎运”的,没想到闭关在家期间,也能收到阿里发来的视频面,还一路顺利拿下了offer。