如何正确遍历删除List中的元素,你会吗?

本文对比分析了 Java 中使用增强 for 循环、普通 for 循环、迭代器等方法遍历删除 List 中元素时的异常表现及正确使用方式,提供了避免 ConcurrentModificationException 的解决方案。

http://haohaoxuexi.iteye.com/blog/1523785


遍历删除List中的元素有很多种方法,当运用不当的时候就会产生问题。下面主要看看以下几种遍历删除List中元素的形式:

1.通过增强的for循环删除符合条件的多个元素

2.通过增强的for循环删除符合条件的一个元素

3.通过普通的for删除删除符合条件的多个元素

4.通过Iterator进行遍历删除符合条件的多个元素

 

 

Java代码   收藏代码
  1. /** 
  2.  * 使用增强的for循环 
  3.  * 在循环过程中从List中删除非基本数据类型以后,继续循环List时会报ConcurrentModificationException 
  4.  */  
  5. public void listRemove() {  
  6.     List<Student> students = this.getStudents();  
  7.     for (Student stu : students) {  
  8.         if (stu.getId() == 2)   
  9.             students.remove(stu);  
  10.     }  
  11. }  

 

 

Java代码   收藏代码
  1. /** 
  2.  * 像这种使用增强的for循环对List进行遍历删除,但删除之后马上就跳出的也不会出现异常 
  3.  */  
  4. public void listRemoveBreak() {  
  5.     List<Student> students = this.getStudents();  
  6.     for (Student stu : students) {  
  7.         if (stu.getId() == 2) {  
  8.             students.remove(stu);  
  9.             break;  
  10.         }  
  11.     }  
  12. }  

 

 

Java代码   收藏代码
  1. /** 
  2.  * 这种不使用增强的for循环的也可以正常删除和遍历, 
  3.  * 这里所谓的正常是指它不会报异常,但是删除后得到的 
  4.  * 数据不一定是正确的,这主要是因为删除元素后,被删除元素后 
  5.  * 的元素索引发生了变化。假设被遍历list中共有10个元素,当 
  6.  * 删除了第3个元素后,第4个元素就变成了第3个元素了,第5个就变成 
  7.  * 了第4个了,但是程序下一步循环到的索引是第4个, 
  8.  * 这时候取到的就是原本的第5个元素了。 
  9.  */  
  10. public void listRemove2() {  
  11.     List<Student> students = this.getStudents();  
  12.     for (int i=0; i<students.size(); i++) {  
  13.         if (students.get(i).getId()%3 == 0) {  
  14.             Student student = students.get(i);  
  15.             students.remove(student);  
  16.         }  
  17.     }  
  18. }  

 

 

Java代码   收藏代码
  1. /** 
  2.  * 使用Iterator的方式也可以顺利删除和遍历 
  3.  */  
  4. public void iteratorRemove() {  
  5.     List<Student> students = this.getStudents();  
  6.     System.out.println(students);  
  7.     Iterator<Student> stuIter = students.iterator();  
  8.     while (stuIter.hasNext()) {  
  9.         Student student = stuIter.next();  
  10.         if (student.getId() % 2 == 0)  
  11.             stuIter.remove();//这里要使用Iterator的remove方法移除当前对象,如果使用List的remove方法,则同样会出现ConcurrentModificationException  
  12.     }  
  13.     System.out.println(students);  
  14. }  

 

 

附完整代码如下:

 

Java代码   收藏代码
  1. import java.util.ArrayList;  
  2. import java.util.Iterator;  
  3. import java.util.List;  
  4.   
  5. public class ListRemove {  
  6.   
  7.     public static void main(String args[]) {  
  8.         ListRemove lr = new ListRemove();  
  9.         lr.listRemove();  
  10.         lr.listRemoveBreak();  
  11. //      lr.listRemove2();  
  12. //      lr.iteratorRemove();  
  13.     }  
  14.   
  15.     /** 
  16.      * 使用增强的for循环 
  17.      * 在循环过程中从List中删除非基本数据类型以后,继续循环List时会报ConcurrentModificationException 
  18.      */  
  19.     public void listRemove() {  
  20.         List<Student> students = this.getStudents();  
  21.         for (Student stu : students) {  
  22.             if (stu.getId() == 2)   
  23.                 students.remove(stu);  
  24.         }  
  25.     }  
  26.       
  27.     /** 
  28.      * 像这种使用增强的for循环对List进行遍历删除,但删除之后马上就跳出的也不会出现异常 
  29.      */  
  30.     public void listRemoveBreak() {  
  31.         List<Student> students = this.getStudents();  
  32.         for (Student stu : students) {  
  33.             if (stu.getId() == 2) {  
  34.                 students.remove(stu);  
  35.                 break;  
  36.             }  
  37.         }  
  38.     }  
  39.       
  40.     /** 
  41.      * 这种不使用增强的for循环的就可以正常删除和遍历 
  42.      */  
  43.     public void listRemove2() {  
  44.         List<Student> students = this.getStudents();  
  45.         for (int i=0; i<students.size(); i++) {  
  46.             if (students.get(i).getId()%2 == 0)  
  47.                 students.remove(i);  
  48.         }  
  49.     }  
  50.       
  51.     /** 
  52.      * 使用Iterator的方式也可以顺利删除和遍历 
  53.      */  
  54.     public void iteratorRemove() {  
  55.         List<Student> students = this.getStudents();  
  56.         System.out.println(students);  
  57.         Iterator<Student> stuIter = students.iterator();  
  58.         while (stuIter.hasNext()) {  
  59.             Student student = stuIter.next();  
  60.             if (student.getId() % 2 == 0)  
  61.                 stuIter.remove();  
  62.         }  
  63.         System.out.println(students);  
  64.     }  
  65.       
  66.     private List<Student> getStudents() {  
  67.         List<Student> students = new ArrayList<Student>() {  
  68.             {  
  69.                 int i = 0;  
  70.                 while (i++ < 10) {  
  71.                     Student student = new Student(i, "201200" + i, "name_" + i);  
  72.                     this.add(student);  
  73.                 }  
  74.             }  
  75.         };  
  76.         return students;  
  77.     }  
  78. }  

 

 

Java代码   收藏代码
  1. public class Student {  
  2.   
  3.     private int id;  
  4.     private String stuNo;  
  5.     private String name;  
  6.       
  7.     public Student() {  
  8.           
  9.     }  
  10.       
  11.     public Student(int id, String stuNo, String name) {  
  12.         this.id = id;  
  13.         this.stuNo = stuNo;  
  14.         this.name = name;  
  15.     }  
  16.   
  17.     public int getId() {  
  18.         return id;  
  19.     }  
  20.   
  21.     public void setId(int id) {  
  22.         this.id = id;  
  23.     }  
  24.   
  25.     public String getStuNo() {  
  26.         return stuNo;  
  27.     }  
  28.   
  29.     public void setStuNo(String stuNo) {  
  30.         this.stuNo = stuNo;  
  31.     }  
  32.   
  33.     public String getName() {  
  34.         return name;  
  35.     }  
  36.   
  37.     public void setName(String name) {  
  38.         this.name = name;  
  39.     }  
  40.   
  41.     @Override  
  42.     public String toString() {  
  43.         return "Student [id=" + id + ", name=" + name + ", stuNo=" + stuNo  
  44.                 + "]";  
  45.     }  
  46.       
  47. }  
<think> 嗯,用户问的是C#的List在foreach遍历删除元素会不会失效。这个问题其实挺常见的,用户可能正在处理一个需要动态删除元素的需求,结果遇到了异常或数据错误。 从用户提供的引用来看,引用[3]和[4]都明确提到了这个问题。引用[4]里的例子很直观,展示了foreach中直接删除会抛出InvalidOperationException。用户可能已经遇到了这个异常,或者在多线程场景下遇到了更隐蔽的问题。 用户特别提到“失效”这个词,说明ta可能理解删除操作会导致迭代器错位。确实如此,.NET的List在foreach时使用迭代器,任何修改集合的操作都会导致迭代器失效。这个设计和其他语言(比如Java的fail-fast机制)是类似的。 值得注意的细节是,引用[2]还提到了多线程场景的危险性。用户虽然没有明确说多线程,但ta提供的引用里提到了Parallel.ForEach,说明ta的应用场景可能涉及并发。这时候问题会更复杂,不仅迭代器失效,还会出现数据竞争。 在思考解决方案时,引用[3]给出的递归方法虽然能解决问题,但效率太低,特别是对于大列表。更优的方案应该是用for循环倒序遍历或者用RemoveAll方法。用户可能更关心实际解决方案的效率,所以回复中要强调时间复杂度。 另外,用户提供的引用里有个有趣的现象:引用[4]展示了用正向for循环删除元素时结果不正确(最后剩50个元素)。这个例子很好地说明了为什么倒序删除更安全,值得在回复中展开说明。 用户应该是个C#中级开发者,知道基本操作但可能不熟悉底层机制。回复时需要解释清楚原理,但避免过于底层的细节。ta的情绪可能有些困扰,因为这个问题容易导致崩溃性错误。 </think> 在C#中,**使用`foreach`遍历`List<T>`时直接删除元素会导致运行时异常(迭代器失效)**。这是由`List<T>`的迭代器机制决定的,具体原因和解决方案如下: --- ### **迭代器失效的原因** 1. **修改检测机制** `foreach`本质使用`IEnumerator`迭代器遍历集合。`List<T>`的迭代器会实时监控集合的修改状态(通过内部的`_version`字段)。**当在`foreach`循环中调用`Remove()`、`Add()`等方法修改集合结构时,下一次迭代会检测到版本号变化,立即抛出`InvalidOperationException`:** > `"Collection was modified; enumeration operation may not execute."` [^4] 2. **下标错位问题** 即使未触发异常(如用`for`循环),正向遍历删除元素也会导致下标错位: ```csharp for (int i = 0; i < integers.Count; i++) { integers.Remove(integers[i]); // 每删除一个元素,后续元素前移,导致跳过下一个元素 } // 结果:只能删除一半元素(如原100个元素剩余50个)[^4] ``` --- ### **安全删除元素的方案** #### ✅ 方案1:倒序`for`循环 ```csharp for (int i = list.Count - 1; i >= 0; i--) { if (condition) list.RemoveAt(i); // 从后向前删除,避免下标错位 } ``` **原理**:倒序删除不会影响未遍历元素的下标。 #### ✅ 方案2:使用`List.RemoveAll()` ```csharp list.RemoveAll(item => item == "b"); // 一行代码删除所有满足条件的元素 ``` **原理**:内部优化了删除逻辑,时间复杂度为$O(n)$,效率最高。 #### ✅ 方案3:创建新集合(适用于复杂逻辑) ```csharp var itemsToRemove = list.Where(item => condition).ToList(); foreach (var item in itemsToRemove) { list.Remove(item); } ``` **注意**:该方法每次调用`Remove()`会遍历列表,时间复杂度为$O(n^2)$,仅适用于小数据集。 #### 避免方案:递归删除(低效且复杂) 引用[3]的递归方法虽能运行,但存在严重缺陷: ```csharp void RemoveTest(List<string> list) { foreach (var item in list) { if (item == "b") { list.Remove(item); RemoveTest(list); // 递归导致多次完整遍历 return; // 退出当前递归层 } } } ``` **问题**:递归调用导致多次遍历,性能极差且代码难以维护。 --- ### **多线程场景的特殊注意** 若在**并行循环(如`Parallel.ForEach`)** 中删除元素: 1. 线程竞争导致数据不一致(如引用[2]的主键重复问题); 2. 需要显式同步锁(`lock`),但会大幅降低并发性能; 3. 推荐方案:分割任务,每个线程操作独立数据集,最后合并结果。 --- ### **关键结论** | 场景 | 是否允许 | 推荐方案 | |--------------------|--------------------------|------------------------------| | `foreach`循环 | ❌ 直接删除必崩溃 | 改用`for`倒序或`RemoveAll` | | 正向`for`循环 | ⚠ 可能漏删或逻辑错误 | 改用倒序`for`循环 | | 多线程环境 | ❌ 需线程同步 | 隔离数据或避免共享集合修改 | > 始终优先选择`RemoveAll()`,其兼顾安全性与性能。若需遍历中修改集合结构,应彻底放弃`foreach`[^3][^4]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值