ArrayList 的删除姿势你都掌握了吗

栏目: IT技术 · 发布时间: 3年前

内容简介:前几天有个读者由于看了我们先看下ArrayList总共有几种删除元素的方法吧。下面我们来分析下为什么这些方法为什么有的可以正确删除元素,有的不可以。引用大佬们经常说的一句话

引言

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

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 ) 先来张图吧,看图更直观。 ArrayList 的删除姿势你都掌握了吗 数组删除元素后每次都需要移动。第一次删除( 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了一个骚操作。 ArrayList 的删除姿势你都掌握了吗

使用迭代器的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();
}
}

这种方法也是《 阿里开发手册 》(需要的可以公众号回复: 泰山 )推荐的。 ArrayList 的删除姿势你都掌握了吗

总结

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

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。

  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。

  • 感谢您的阅读,十分欢迎并感谢您的关注。

  • ArrayList 的删除姿势你都掌握了吗

往期推荐

小白都能看的懂的多线程

万恶的NPE差点让我半个月工资没了

终于有人把 java 代理 讲清楚了,万字详解!

价值19999元架构师学习资料


以上所述就是小编给大家介绍的《ArrayList 的删除姿势你都掌握了吗》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

软技能

软技能

John Sonmez / 王小刚 / 人民邮电出版社 / 2016-7 / 59.00元

这是一本真正从“人”(而非技术也非管理)的角度关注软件开发人员自身发展的书。书中论述的内容既涉及生活习惯,又包括思维方式,凸显技术中“人”的因素,全面讲解软件行业从业人员所需知道的所有“软技能”。本书聚焦于软件开发人员生活的方方面面,从揭秘面试的流程到精耕细作出一份杀手级简历,从创建大受欢迎的博客到打造你,从提高自己工作效率到与如何与“拖延症”做斗争,甚至包括如何投资不动产,如何关注自己的健康。本......一起来看看 《软技能》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器