List在遍历中删除t元素

本文介绍了三种在Java中从List中删除元素的方法,并详细解释了每种方法的注意事项,包括使用普通for循环、增强型for循环及Iterator迭代器进行删除操作的特点与限制。

 

法一:使用普通for循环遍历

注意: 1.从头开始循环,每次删除后 i  减一。
            2.从尾开始循环。
 
  1. public class Main {  
  2.     public static void main(String[] args) throws Exception {  
  3.         List<Integer> list = new CopyOnWriteArrayList<>();  
  4.         for (int i = 0; i < 5; i++)  
  5.             list.add(i);  
  6.         for (int i = 0; i < list.size(); i++) { 
  7.             System.out.print(i + " " + list.get(i));  
  8.             if (list.get(i) % 2 == 0) {  
  9.                 list.remove(list.get(i));  
  10.                 System.out.print(" delete");  
  11.                 i--; // 索引改变!  
  12.             }  
  13.             System.out.println();  
  14.         }  
  15.     }  
  16. }  
 

法二:使用增强型for循环遍历

注意:不使用CopyOnWriteArrayList可以看到删除第一个元素时是没有问题的,但删除后继续执行遍历过程的话就会抛出ConcurrentModificationException的异常。

原因:因为元素在使用的时候发生了并发的修改,导致异常抛出。但是删除完毕马上使用break跳出,则不会触发报错

  1. public class Main {  
  2.     public static void main(String[] args) throws Exception {  
  3.         List<Integer> list = new CopyOnWriteArrayList<>();  
  4.         for (int i = 0; i < 5; i++)  
  5.             list.add(i);  
  6.         // list {0, 1, 2, 3, 4}  
  7.         for (Integer num : list) {  
  8.             // index and number  
  9.             System.out.print(num);  
  10.             if (num % 2 == 0) {  
  11.                 list.remove(num);  
  12.                 System.out.print(" delete");  
  13.             }  
  14.             System.out.println();  
  15.         }  
  16.     }  
  17. }  

法三:使用iterator遍历

注意:使用iterator的remove()而不是list的remove()方法
 
  1. public class Main {  
  2.     public static void main(String[] args) throws Exception {  
  3.         List<Integer> list = new ArrayList<>();  
  4.         for (int i = 0; i < 5; i++)  
  5.             list.add(i);  
  6.         // list {0, 1, 2, 3, 4}  
  7.         Iterator<Integer> it = list.iterator();  
  8.         while (it.hasNext()) {  
  9.             // index and number  
  10.             int num = it.next();  
  11.             System.out.print(num);  
  12.             if (num % 2 == 0) {  
  13.                 it.remove();  
  14.                 System.out.print(" delete");  
  15.             }  
  16.             System.out.println();  
  17.         }  
  18.     }  
  19. }  
 
 

 

转载于:https://www.cnblogs.com/Pjson/p/8548966.html

<think> 嗯,用户问的是C#的List在foreach遍历删除元素会不会失效。这个问题其实挺常见的,用户可能正在处理一个需要动态删除元素的需求,结果遇到了异常或数据错误。 从用户提供的引用来看,引用[3]和[4]都明确提到了这个问题。引用[4]里的例子很直观,展示了foreach中直接删除会抛出InvalidOperationException。用户可能已经遇到了这个异常,或者在多线程场景下遇到了更隐蔽的问题。 用户特别提到“失效”这个词,说明ta可能理解删除操作会导致迭代器错位。确实如此,.NETList在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]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值