ArrayList遍历删除方式总结


我们知道,在Java语言当中对集合的遍历方式大致分为3种:fori,foreach,iterator。假如给定一个ArrayList,对其中的某些指定元素进行循环遍历查找并且删除的话,它们之间又有什么不同呢?
首先构造一个ArrayList:

private static List<String> list = new ArrayList<>(5);
static {
   list.add("add");
   list.add("delete");
   list.add("delete");
   list.add("update");
   list.add("query");
}

1. fori 删除(顺序会漏删,倒序可正常)

顺序删除
@Test
public void foriDelete(){
    for (int i = 0; i < list.size(); i++) {
        if ("delete".equals(list.get(i))){
            list.remove(i);
        }
    }
    System.out.println("顺序删除之后的结果为-->"+list.toString());
    //顺序删除之后的结果为-->[add, delete, update, query]
}

上述可以看出,其中有一个delete字符串没有被删除掉,这是因为ArrayList本身是个数组,在进行遍历的时候,如果删除某个元素,则后续的元素需要整体往前移动,当循环到i=1的时候,找到了第一个delete字符串,然后删除,第2个delete以及之后的字符串会向前移动,这个时候第2个delete就在数组的1位置了。当循环到i=2的时候,正好把原来位置的delete给略过去了,导致漏删:
数组删除元素

针对这个问题,我们可以依据删除的特点进行倒序删除,这样就可以找到delete字符串的正确位置了:

倒序删除
@Test
public void foriDelete(){
    for (int i = list.size() - 1; i >= 0; i--) {
        if ("delete".equals(list.get(i))){
            list.remove(i);
        }
    }
    System.out.println("倒序删除之后的结果为-->"+list.toString());
    //倒序删除之后的结果为-->[add, update, query]
}

2. foreach 删除(删除一个元素停止遍历可正常,多个元素删除有CME问题)

@Test
public void foreachDelete(){
	for(String s:list){
		if(s.equals("delete")){
			list.remove(s);
		}
	}
	System.out.println("foreach删除之后的结果为-->"+list.toString());
	//删除出错:java.util.ConcurrentModificationException
}

首先得明确一点,foreach的原理其实是编译器会将其编译为迭代器Iterator的形式进行遍历,以下是对.class文件反编译之后的代码:

 @Test
public void foreachDelete() {
    Iterator var1 = list.iterator();
    while(var1.hasNext()) {
        String s = (String)var1.next();
        if (s.equals("delete")) {
            list.remove(s);
        }
    }
}

但与真正的使用迭代器遍历的方式有点不同,删除的时候不是使用迭代器的remove方法,而是用的ArrayList的remove方法。为什么会产生ConcurrentModificationException呢?这是由于,ArrayList本身不是线程安全的,在使用迭代器遍历查询的时候,会有一个检查机制,来确保一个线程在遍历的时候,其他线程不会删除该集合中的元素。遍历代码如下:

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];
}
/**确保List在同一时刻不会有多个线程进行删除*/
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

而当使用foreach方式删除元素的时候,调用ArrayList的remove方法如下:

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 void fastRemove(int index) {
	//快速删除时,会更改此值
    modCount++;
    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
}

当在第一次找到delete字符串,并进行删除的时候,会对modCount++。如果没有停止该遍历,则在下次循环的时候,会校验modCount与 expectedModCount是否相等。若不等,则抛出并发修改异常。
所以,foreach循环删除元素,是可以的,但有个条件:只能删除一个元素,并立即停止遍历,在remove下加上break即可。

3. iterator 删除(可正常删除)

@Test
public void iteratorDelete(){
	Iterator<String> it = list.iterator();
	while(it.hasNext()){
		String x = it.next();
		if(x.equals("delete")){
			it.remove();
		}
	}
	System.out.println("iterator删除之后的结果为-->"+list.toString());
	//iterator删除之后的结果为-->[add, update, query]
}

ArrayList在使用迭代器Iterator进行删除的时候,逻辑如下:

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

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

可以看出来,在进行删除的时候,会将modCount赋值给expectedModCount,所以不会导致两者不等。只要不是数组越界,就不会报出ConcurrentModificationException了。

通过以上的分析可以看出,在不同的场景下需要选择合适的方式,当然,Iterator遍历删除是不需要担心的。

### 回答1: 要从 ArrayList 删除指定元素,可以使用 remove() 方法。该方法可以接收一个参数,即要删除的元素。示例代码如下: ``` ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); list.add(4); System.out.println("原始列表:" + list); list.remove(Integer.valueOf(2)); System.out.println("删除元素 2 后的列表:" + list); ``` 在上面的代码,我们先创建了一个包含 1、2、3、4 四个元素的 ArrayList 对象。接着,我们使用 remove() 方法删除了列表的元素 2。最后,输出删除元素后的列表。需要注意的是,remove() 方法的参数需要使用 Integer.valueOf() 方法将 int 类型的元素转换为 Integer 类型的对象。 ### 回答2: ArrayList是Java的一个动态数组,可以存储任意类型的数据。要删除ArrayList的指定元素,可以通过以下步骤实现: 1. 首先,创建一个ArrayList对象,并向其添加元素,假设我们要删除的元素是"apple"。 ``` java ArrayList<String> fruits = new ArrayList<String>(); fruits.add("apple"); fruits.add("banana"); fruits.add("orange"); ``` 2. 使用remove方法删除指定元素,该方法接受要删除的元素作为参数。在删除之前,我们可以使用contains方法来检查元素是否存在于ArrayList。 ``` java if (fruits.contains("apple")) { fruits.remove("apple"); } ``` 上述代码段会检查ArrayList是否存在"apple",如果存在,则使用remove方法删除该元素。 3. 如果ArrayList有多个相同的元素,remove方法只会删除第一个匹配的元素。如果想删除所有匹配的元素,可以使用循环进行遍历删除。 ``` java for (int i = 0; i < fruits.size(); i++) { if (fruits.get(i).equals("apple")) { fruits.remove(i); i--; // 同时删除一个元素后,需要将索引减1 } } ``` 上述代码会遍历整个ArrayList,如果找到匹配的元素,就使用remove方法删除,并将索引减1以保证下一个元素不被漏掉。 4. 在删除元素后,可以使用size方法来检查ArrayList的大小。 ``` java System.out.println(fruits.size()); ``` 上述代码会输出ArrayList剩余元素的个数。 总结:通过使用ArrayList的remove方法,可以删除指定元素。如果ArrayList有多个相同的元素,可以使用循环来删除所有匹配的元素。 ### 回答3: 要从ArrayList删除指定的元素,可以使用ArrayList的remove()方法。这个方法有两个重载版本:一个接受一个索引值作为参数,另一个接受一个对象作为参数。 如果你知道要删除元素的索引值,可以使用remove(int index)方法。这将删除ArrayList指定索引位置的元素,并且将后面的元素向前移动一个位置来填补空缺。例如,如果要删除索引为2的元素,可以使用以下代码: ArrayList<String> list = new ArrayList<>(); list.add("元素1"); list.add("元素2"); list.add("元素3"); list.remove(2); 如果你知道要删除的元素的具体值,可以使用remove(Object obj)方法。这个方法将删除ArrayList第一个找到的匹配元素。例如,如果要删除值为"元素2"的元素,可以使用以下代码: ArrayList<String> list = new ArrayList<>(); list.add("元素1"); list.add("元素2"); list.add("元素3"); list.remove("元素2"); 无论使用哪种方法,删除元素后,ArrayList的大小会相应地减小1,并且后面的元素会向前移动以填补空缺。如果ArrayList不存在要删除的元素,那么不会进行任何操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值