在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出bug。不妨把这个问题当做一道面试题目,我想一定能难道不少的人。今天就给大家说一下在ArrayList循环遍历并删除元素的问题。首先请看下面的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
import java.util.ArrayList;
publicclassArrayListRemove
{
publicstaticvoidmain(String[]args)
{
ArrayList<String>list=newArrayList<String>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
list.add("c");
list.add("c");
remove(list);
for(Strings:list)
{
System.out.println("element : "+s);
}
}
publicstaticvoidremove(ArrayList<String>list)
{
// TODO:
}
}
|
如果要想删除list的b字符,有下面两种常见的错误例子:
错误写法实例一:
|
publicstaticvoidremove(ArrayList<String>list)
{
for(inti=0;i<list.size();i++)
{
Strings=list.get(i);
if(s.equals("b"))
{
list.remove(s);
}
}
}
|
错误的原因:这种最普通的循环写法执行后会发现第二个“b”的字符串没有删掉。
错误写法实例二:
|
publicstaticvoidremove(ArrayList<String>list)
{
for(Strings:list)
{
if(s.equals("b"))
{
list.remove(s);
}
}
}
|
错误的原因:这种for-each写法会报出著名的并发修改异常:java.util.ConcurrentModificationException。
先解释一下实例一的错误原因。翻开JDK的ArrayList源码,先看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
publicbooleanremove(Objecto){
if(o==null){
for(intindex=0;index<size;index++)
if(elementData[index]==null){
fastRemove(index);
returntrue;
}
}else{
for(intindex=0;index<size;index++)
if(o.equals(elementData[index])){
fastRemove(index);
returntrue;
}
}
returnfalse;
}
|
一般情况下程序的执行路径会走到else路径下最终调用faseRemove方法:
|
privatevoidfastRemove(intindex){
modCount++;
intnumMoved=size-index-1;
if(numMoved>0)
System.arraycopy(elementData,index+1,elementData,index,numMoved);
elementData[--size]=null;// Let gc do its work
}
|
可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。针对错误写法一,在遍历第一个字符串b时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也就是第二个字符串b)至当前位置,导致下一次循环遍历时后一个字符串b并没有遍历到,所以无法删除。针对这种情况可以倒序删除的方式来避免:
|
publicstaticvoidremove(ArrayList<String>list)
{
for(inti=list.size()-1;i>=0;i--)
{
Strings=list.get(i);
if(s.equals("b"))
{
list.remove(s);
}
}
}
|
因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。
接着解释一下实例二的错误原因。错误二产生的原因却是foreach写法是对实际的Iterable、hasNext、next方法的简写,问题同样处在上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器(该代码在其父类AbstractList中):
|
publicIterator<E>iterator(){
returnnewItr();
}
|
这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator,看这个类的next方法:
|
publicEnext(){
checkForComodification();
try{
Enext=get(cursor);
lastRet=cursor++;
returnnext;
}catch(IndexOutOfBoundsExceptione){
checkForComodification();
thrownewNoSuchElementException();
}
}
|
第一行checkForComodification方法:
|
finalvoidcheckForComodification(){
if(modCount!=expectedModCount)
thrownewConcurrentModificationException();
}
|
这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或for-each的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。
1
2
3
4
5
6
7
8
9
10
11
12
|
publicstaticvoidremove(ArrayList<String>list)
{
Iterator<String>it=list.iterator();
while(it.hasNext())
{
Strings=it.next();
if(s.equals("b"))
{
it.remove();
}
}
|
分享到:
相关推荐
"QT遍历删除文件夹下所有文件及目录" 在QT应用程序中,需要遍历删除文件夹下所有文件及目录是一种常见的操作。本文将详细介绍如何使用QT框架实现该功能。 一、使用QDir和QFileInfo遍历删除文件夹 在QT中,可以...
一次遍历单链表删除倒数第n个节点的问题,跟删除某个节点的前一个节点是一个思路
当我们需要遍历List并根据条件删除特定元素时,需要注意正确的方法,以避免在遍历过程中出现错误。以下将详细介绍如何在C#中遍历List并删除元素,包括正序和倒序遍历的技巧。 首先,我们来看一下错误的遍历方式。...
asp遍历文件夹删除多余文件,asp遍历文件夹删除多余文件
本程序旨在通过实现二叉树的创建、遍历、查找以及删除等基本操作,帮助学生深入理解和掌握二叉树的相关知识。 首先,我们要理解什么是二叉树。二叉树是一种特殊的树形数据结构,其中每个节点最多有两个子节点,通常...
如果从头到尾正序遍历删除的话,有些符合删除条件的元素会成为漏网之鱼; 正序删除举例: List<string> tempList = new List() { "a","b","b","c" }; for (int i = 0; i < tempList.Count; i++) { if ...
二叉树的遍历有多种方式,本文将介绍二叉树的递归遍历,中序遍历,先序遍历和后序遍历。 递归遍历是指使用递归函数来遍历二叉树的每个结点。递归遍历的优点是代码简洁易懂,但缺点是可能会导致堆栈溢出。在本文的...
二叉树的建立查找遍历删除的程序的实现,同志们加油
HashMap和List遍历方法及如何遍历删除元素总结 HashMap和List都是Java中最常用的数据结构,它们都可以用来存储和操作数据。然而,在遍历和删除元素时,需要小心地处理,以免出现问题。下面总结了HashMap和List的...
二叉树的基本操作主要包括创建、插入、删除节点以及遍历等。创建二叉树时,需要指定根节点,之后通过插入操作来构造完整的二叉树结构。删除节点时要考虑节点是否有子节点,以及如何调整树的结构以保持二叉树的特性。...
"图的深度优先遍历和广度优先遍历算法" 图的深度遍历和广度遍历是两个重要的算法,这也是我们理解并掌握图这一数据结构的基础。通过此程序算法可以进一步掌握图的构造以及遍历的相关知识。 图的深度优先遍历算法 ...
用C++写的二叉树先序遍历、中序遍历和后序遍历非递归算法
List集合遍历和删除操作
在二叉树的遍历中,通常有三种主要的遍历方法:前序遍历、中序遍历和后序遍历。这些遍历方式都是基于二叉树的递归定义,即每个节点包含一个值、一个左子节点和一个右子节点。 1. **前序遍历(Preorder Traversal)**...
Java HashMap 遍历和删除元素方法小结 Java HashMap 是一种常用的数据结构,用于存储键值对儿,但是在遍历和删除元素时,需要注意一些特殊的情况,否则可能会出现异常或错误。本文将介绍 Java HashMap 遍历和删除...
除了基础的遍历,你还可以根据实际需求进行扩展,比如过滤特定类型的文件,或者统计文件数量,甚至对文件进行更复杂的操作,如复制、移动或删除。 在实际开发中,需要注意的是,遍历目录文件时要处理可能出现的错误...
如果在遍历过程中删除元素,通常需要使用迭代器(Iterator)来安全地进行,因为迭代器会跟踪当前元素并处理删除后的调整。 最后,输出了ArrayList的大小以及所有学生的姓名、学号和颜色。遍历结束后,输出显示...
课后作业:层次遍历先序遍历
二叉树的遍历主要有三种经典方法:前序遍历、中序遍历和后序遍历。这些遍历方法是理解二叉树特性和操作的关键。 1. **前序遍历**(Preorder Traversal): - 遵循“根-左-右”的顺序访问节点。首先访问根节点,...
对于已知的前序遍历(P遍历)和中序遍历(I遍历),我们可以通过以下步骤打印出后序遍历(H遍历): 1. **初始化**:创建一个空栈,记录当前中序遍历中的位置(初始为0)。 2. **匹配前序遍历**:遍历P遍历,每遇到...