26

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

 4 years ago
source link: https://mp.weixin.qq.com/s/RX6LmCU-jBPPYDW2W-7XXw
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

引言

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

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 ) 先来张图吧,看图更直观。 Jb2QR3.png!web 数组删除元素后每次都需要移动。第一次删除( 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了一个骚操作。 rQVr2yb.png!web

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

这种方法也是《 阿里开发手册 》(需要的可以公众号回复: 泰山 )推荐的。 RZzmu2.png!web

总结

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

结束

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

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

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

  • 2yInMrZ.png!web

往期推荐

小白都能看的懂的多线程

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

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

价值19999元架构师学习资料


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK