foreach 循环里进行元素的 remove/add 操作

不要在 foreach 循环里进行元素的 remove/add 操作

1、remove 元素请使用 Iterator方式,如果并发操作,需要对Iterator对象加锁

// A code block
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
if ("1".equals(temp)) {
a.remove(temp);
}
}

说明:如果将if (“1”.equals(temp)) { 里面的 1 换成 2的话,会报下面这个错误

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at com.song.pojo.Test01.test01(Test01.java:28)
	at com.song.pojo.Test01.main(Test01.java:17)

**原因:**所以,之所以会抛出ConcurrentModificationException异常,是因为代码中使用了foreach循环,而在foreach循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用集合类自己的方法,这就导致Iterator在遍历的时候会发现有一个元素在自己不知不觉的情况下被添加/删除了,就会抛出一个异常,用来提示用户可能发生了并发修改。

2、解决办法?

2-1、直接使用普通for循环 遍历
因为普通for循环并没有使用Iterator的遍历,所以压根就没有进行fail-fast的检验
2-2、直接使用Iterator 遍历
如何直接使用iterator提供的add/remove方法,那么就可以修改到expectedModCount的值,那就不会再抛出这个异常了,其实现的代码如下:

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();
	}
 }

2-3、使用Java 8中提供的filter过滤:
Java 8中可以把集合转换成流,对于流有一种filter操作,可以对原始Stream进行某项测试,通过测试的元素被留下来生成一个新的流:

public static void main(String[] args) {
		List<String> userNames = new ArrayList<String>();
		userNames.add("Tom");
		userNames.add("Jack");
		userNames.add("Mic");
		userNames.add("Seven");
		userNames = userNames.stream().filter(userName -> !userName.equalsIgnoreCase("Jack")).collect(Collectors.toList());
		System.out.println(userNames);
	}

2-4、直接使用fail-safe的集合类:
在Java中,除了一些普通的集合类以外,还有一些采用了fail-safe机制的集合类,这样的集合容器在遍历时不时直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
由于迭代是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所做的修改并不能被迭代器检测到,所以不会抛出ConcurrentModificationException。

public static void main(String[] args) {
		ConcurrentLinkedDeque<String> userNames = new ConcurrentLinkedDeque<String>();
		userNames.add("Tom");
		userNames.add("Jack");
		userNames.add("Mic");
		userNames.add("Seven");
		for (String userName : userNames) {
			if (userName.equals("Jack")) {
				userNames.remove(userName);
			}
		}
		System.out.println(userNames);
	}

基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

以上几种方式都可以避免触发fail-fast机制,避免抛出异常。如果是并发场景,建议使用concurrent包中的容器;如果是单线程场景,Java8之前的代码中建议使用Iterator进行元素删除/添加;Java8之后可以考虑使用Stream及filter.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
java 开发者 配置jdk1.8 一下是jdk1.8的新特性Lamdbd表达式 forEach循环遍历 List<String> names = new ArrayList(); names.forEach(String x -> { System.out.println(x); System.out.println(x); }); Map<String,String> map = new HashMap(); map.forEach((x,y) -> System.out.println(x+":"+y)); 集合放对象 List<Book> books; books.forEach(x -> System.out.println(x.getBookName())); List<Map> books = new ArrayList(); Map map = new HashMap(); map.put("name","aaa"); map.put("price",33); books.forEach(x -> x.forEach(y,z) -> System.out.println(y+":"+z)); 方法引用 names.forEach(System.out::println); 等同于 names.forEach(x->System.out.println(x)) removeIf 条件移除 List<Book> books; books.removeIf(x -> x.getBookPrice() > 22); stream 对集合进行处理,并不会改变集合,类似于数据库的select 创建stream 集合.stream() 操作stream filter 过滤 map 转换 skip 跳过 distinct 去重 limit 取几条 结果处理 foreach 输出 collect 收集 count 总条数 List<String> names = ; names.add("zhangsan"); names.add("zhangsan"); List nNews = names.stream.distinct().collect(Collectors.toList()); Collectors.toSet() //取出名字长度大于4的,跳过第1个,取2个 List<String> nNames = names.stream.filter(x->x.length()>4).skip(1),limt(2).collect(Collectors.toList()); //把长度转换成一个集合 List<Integer> c = names.stream.map(x->x.length()).collect(Collectors.toList()); List<Integer> c = names.stream.map(String::length).collect(Collectors.toList());
C#5.0本质论第四版,高清扫描的,对C#5.0技术讲的比较详细,第1章 C#概述 1 1.1 Hello,World 1 1.2 C#语法基础 3 1.2.1 C#关键字 3 1.2.2 标识符 4 1.2.3 类型定义 5 1.2.4 Main 6 1.2.5 语句和语句分隔符 7 1.2.6 空白 8 1.2.7 使用变量 8 1.2.8 数据类型 9 1.2.9 变量的声明 9 1.2.10 变量的赋值 10 1.2.11 变量的使用 11 1.3 控制台输入和输出 11 1.3.1 从控制台获取输入 11 1.3.2 将输出写入控制台 12 1.3.3 注释 14 1.3.4 托管执行和公共语言基础结构 16 1.3.5 C#和.NET版本 17 1.3.6 CIL和ILDASM 18 1.4 小结 20 第2章 数据类型 21 2.1 基本数值类型 21 2.1.1 整数类型 22 2.1.2 浮点类型 23 2.1.3 decimal类型 23 2.1.4 字面值 24 2.2 更多基本类型 27 2.2.1 布尔类型 27 2.2.2 字符类型 27 2.2.3 字符串 29 2.3 null和void 34 2.3.1 null 34 2.3.2 void 35 2.4 类型的分类 37 2.4.1 值类型 37 2.4.2 引用类型 37 2.5 可空修饰符 38 2.6 数据类型之间的转换 39 2.6.1 显式转型 39 2.6.2 隐式转型 41 2.6.3 不使用转型操作符的类型转换 42 2.7 数组 43 2.7.1 数组的声明 44 2.7.2 数组的实例化和赋值 45 2.7.3 数组的使用 48 2.7.4 字符串作为数组使用 52 2.7.5 常见数组错误 53 2.8 小结 55 第3章 操作符和控制流 57 3.1 操作符 57 3.1.1 一元操作符正和负 58 3.1.2 二元算术操作符 58 3.1.3 复合赋值操作符 64 3.1.4 递增和递减操作符 65 3.1.5 常量表达式和常量符号 68 3.2 控制流程概述 69 3.2.1 if语句 70 3.2.2 嵌套if 71 3.3 代码块 73 3.4 代码块、作用域和声明空间 74 3.5 布尔表达式 76 3.5.1 关系操作符和相等性操作符 77 3.5.2 逻辑布尔操作符 77 3.5.3 逻辑求反操作符 78 3.5.4 条件操作符 79 3.5.5 空接合操作符 80 3.6 按位操作符 80 3.6.1 移位操作符 81 3.6.2 按位操作符 82 3.6.3 按位赋值操作符 83 3.6.4 按位取反操作符 84 3.7 控制流语句(续) 84 3.7.1 while和do while循环 84 3.7.2 for循环 86 3.7.3 foreach循环 88 3.7.4 switch语句 90 3.8 跳转语句 92 3.8.1 break语句 92 3.8.2 continue语句 94 3.8.3 goto语句 95 3.9 C#预处理指令 97 3.9.1 排除和包含代码 98 3.9.2 定义预处理符号 98 3.9.3 生成错误和警告 99 3.9.4 关闭警告消息 99 3.9.5 nowarn:选项 99 3.9.6 指定行号 100 3.9.7 可视编辑器提示 100 3.10 小结 101 第4章 方法和参数 103 4.1 方法的调用 104 4.1.1 命名空间 105 4.1.2 类型名称 106 4.1.3 作用域 107 4.1.4 方法名称 107 4.1.5 形参和实参 107 4.1.6 方法返回值 107 4.1.7 语句与方法调用的比较 108 4.2 方法的声明 108 4.2.1 形式参数声明 109 4.2.2 方法返回类型声明 110 4.3 using指令 111 4.4 Main()的返回值和参数 114 4.5 方法的参数 116 4.5.1 值参数 116 4.5.2 引用参数(ref) 117 4.5.3 输出参数(out) 118 4.5.4 参数数组 120 4.6 递归 122 4.7 方法重载 124 4.8 可选参数 126 4.9 用异常实现基本错误处理 129 4.9.1 捕捉错误 130 4.9.2 使用throw语句报告错误 136 4.10 小结 138 第5章 类 139 5.1 类的定义和实例化 141 5.2 实例字段 144 5.2.1 实例字段的声明 144 5.2.2 实例字段的访问 144 5.3 实例方法 145 5.4 使用this关键字 146 5.5 访问修饰符 151 5.6 属性 153
求交集和并集的线性算法(原创) 对于给定的两个集合,使用哈希表可以在线性时间复杂度内得到他们的交集和并集,具体说明如下: 假设有集合A={1, 7, 5, 13, 9, 10, 11}, B={5, 7, 10, 1, 18, 12}, 1)求交集,需要得到结果:A∩B={1, 5, 7,10} 思路如下: ①建立一个哈希表(HashTable),其键(KEY)表示集合中数字的值,其值(VALUE)表示集合中数字出现的次数 ②遍历集合A,将集合中的每个数字(KEY)插入哈希表,每个数字的出现次数(VALUE)设置为1 ③遍历集合B,对于集合中的每个数字: 如果哈希表中已经存在该数字,将对应的VALUE改为2 如果哈希表中不存在该数字,忽略 ④遍历哈希表,输出VALUE为2的数字,即得到A和B的交集 2) 求并集,需要得到结果:AUB={1,5,7,9,10,11,12,13,18} 思路如下: ①建立一个哈希表(HashTable),其键(KEY)表示集合中数字的值,其值(VALUE)可以无视 ②遍历集合A,将集合中的每个数字(KEY)插入哈希表 ③遍历集合B,对于集合中的每个数字: 如果哈希表中已经存在该数字,忽略 如果哈希表中不存在该数字,将这个数字插入哈希表 ④遍历哈希表,输出哈希表中的每个KEY,即为A和B的并集 上面以两个集合为例说明了交集和并集的求法,事实上,上述算法可以很方便的扩展到3个或3个以上的集合 的求交集和求并集。另外求并集时,由于哈希表的值(VALUE)部分不需要用到,所以这个数据结构也可以更换为 哈希集(HashSet)。 转载请注明出处。 VB中HashTable 2012-08-20 14:43:21| 分类: asp.net|举报|字号 订阅 首先定义一个hashtable Dim hstl As New Hashtable hstl.Add(key, value) 'java是用.put MS开始全面模仿java 这说说vb.net中的hashtable基本用法: 添加值:hstl.add(key,value) 通过key取值: hstl.Item(key).ToString 判断是否含有Key: ContainsKey(key) 判断是否含有value: ContainsValue(value) 遍历hashtable: Dim de As DictionaryEntry '泛型类 For Each de In hstl console.write(de.key & de.value) Next de hashtable不支持通过value取key. 求2个集合的交集 第一种方法 最简单、粗暴的循环遍历2个集合,判断如果有相同的元素就取出来。假设集合1的长度为M,集合2的长度为N,那么,时间复杂度为:O(M*N) 代码: public static List<string> GetIntersection(List<string> list1, List<string> list2) { List<string> list3 = new List<string>(); //第一种方法:循环遍历 //O(n×m) for (int i = 0; i < list1.Count; i++) { for (int j = 0; j < list2.Count; j++) { if (list1[i]==list2[j]) { list3.Add(list1[i]); } } } return list3; } 第二种方法 利用hash这种很有用的数据结构来实现。我们知道,hash的特点之一就是不允许有重复元素,即hash表中的元素都是唯一的。所以,我们的思路就是:先把第一个集合的所有元素都放进hashSet中,时间复杂度O(M);再把第二个集合中的元素放进hashSet中,如果有重复元素,就是这2个集合的交集,时间复杂度为O(N)。即总的时间复杂度从O(M*N)降低到了O(M+N)。 代码: public static List<string> GetIntersection2(List<string> list1, List<string> list2) { //第二种方法:hash List<string> list3 = new List<string>(); HashSet<string> hashSet = new HashSet<string>(); foreach (string item in list1) { hashSet.Add(item); } foreach (string item in list2) { if (hashSet.Add(item) == false) { list3.Add(item); } } return list3; } 测试 代码: static void Main(string[] args) { List<string> list1 = new List<string>(); list1.Add("apple"); list1.Add("banana"); list1.Add("pear"); list1.Add("orange"); list1.Add("grape"); List<string> list2 = new List<string>(); list2.Add("nokia"); list2.Add("sumsung"); list2.Add("htc"); list2.Add("apple"); list2.Add("orange"); List<string> list =new List<string>(); //test for two set join //list = TwoSetsIntersection.GetIntersection(list1, list2); list = TwoSetsIntersection.GetIntersection2(list1, list2); foreach (string item in list) { Console.Write(item + "\t"); } } 总结 hash的另一个特点是查找效率为O(1),惊人的高! 对于这道题目要是算出来O(M*N)的同学就应该补课了。出来混,迟早要还的。 HashSet<T>类 HashSet<T>类主要是设计用来做高性能集运算的,例如对两个集合求交集、并集、差集等。集合中包含一组不重复出现且无特性顺序的元素。 HashSet<T>的一些特性如下: 1、HashSet<T>中的值不能重复且没有顺序。 2、HashSet<T>的容量会按需自动添加。 构造方法: HashSet() 默认相等比较器创建一个空的新实例。 HashSet(IEnumerable<T> collection)  把指定集合中的collection中的数据复制到集中 HashSet(IEqualityComparer<T> comparer)  使用指定的相等比较器创建一个空的新实例 HashSet(IEnumerable<T> collection,IEqualityComparer<T> comparer)  使用指定的比较器实例化数据,且将指定集合中的元素复制到集合中。 因为HashSet<T>是专门设计来做集合运算的,因此它提供的方法中有不少是和集合运算相关的。 以下给出它的一些常用方法介绍 成员        类型        说明 Add        方法        将指定的元素添加到集合中 Clear        方法         清空集合中的所有元素 Contains     方法         确定某元素是否在HashSet<T>中 Exists       方法         确定HashSet<T>是否包含于指定条件相匹配的元素 ExceptWith    方法         从当前HashSet<T>移除指定集合中的所有元素 IntersectWith   方法        修改当前的HashSet<T>对象,以仅包含该对象和指定集合中存在的元素 IsProperSubsetOf  方法        确定HashSet<T>对象是否为指定集合的真子集 IsProperSupersetOf 方法        确定HashSet<T>对象是否为指定集合的真超集 IsSunsetOf     方法         确定HashSet<T>对象是否为指定集合的子集 IsSupersetOf    方法         确定HashSet<T>对象是否为指定集合的超集 Remove      方法         从HashSet<T>对象中移除指定的元素 RemoveWhere   方法         从HashSet<T>集合中移除与指定谓词所定义的条件相匹配的所有元素 SetEquals     方法         确定HashSet<T>对象与指定的集合中是否包含相同的元素 SynmmetricExceptWith  方法     修改当前的HashSet<T>对象,以仅包含该对象或指定集合中存在的元素 TrimExcess    方法         将HashSet<T>对象的容量设置为它所包含的元素的实际个数,向上舍入为接近的特性与实现的值。 UnionWith     方法         修改当前的HashSet<T>对象,以包含该对象本身和指定集合中存在的所有元素 给个简单的例子,写不完的,总之记得HashSet<T>主要的作用是用来进行,交集、并集等运算的就OK了。 static void Main(string[] args) { HashSet<string> hs = new HashSet<string>(); hs.Add("你"); hs.Add("好"); hs.Add("吗"); HashSet<string> hs1 = new HashSet<string>(); hs1.Add("你"); hs1.Add("好"); bool b = hs1.IsProperSubsetOf(hs); //确定hs1是否是hs的真子集 Console.WriteLine(b); //输出True HashSet<string> hs2 = new HashSet<string>(); hs2.Add("爱你"); IEnumerable<string> list = hs.Union(hs2); //返回并集 foreach (string str in list) { Console.WriteLine(str); //输出 你 好 吗 爱你 } Console.ReadKey(); }
.net C#线程超时的解决方案,使用的时候在被调线程入口调用一下这个方法就可以。更多详细代码见附件 Report.RegisterThread(Report.GetCurrentWin32ThreadID(),Thread.CurrentThread); #region 获取当取线程的ThreadID [DllImport("Kernel32", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)] public static extern Int32 GetCurrentWin32ThreadID(); #endregion #region 登记访问任务子线程 /// <summary> /// 访问任务子线程 /// </summary> public static Hashtable TaskThreadIDTable = Hashtable.Synchronized(new Hashtable()); private static int[] TaskThreadIDs { get { int[] IDs = new int[TaskThreadIDTable.Keys.Count]; TaskThreadIDTable.Keys.CopyTo(IDs, 0); return IDs; } } public static void RegisterThread(int _threadid, System.Threading.Thread thread) { if (!TaskThreadIDTable.ContainsKey(_threadid)) TaskThreadIDTable.Add(_threadid, thread); if (!ExitInvalidThreadLoopRunning) { Thread ExitInvalidThreadLoopThread = new Thread(new ThreadStart(ExitInvalidThreadLoop)); ExitInvalidThreadLoopThread.Priority = ThreadPriority.AboveNormal; ExitInvalidThreadLoopThread.Start(); } } #endregion #region 关闭,退出超时的用户线程 private static DateTime ExitInvalidThreadLoop_LastRunTime = DateTime.Now.Subtract(new TimeSpan(1, 0, 0, 0, 0)); private static bool ExitInvalidThreadLoopRunning { get { return DateTime.Now.Subtract(ExitInvalidThreadLoop_LastRunTime).TotalMinutes < 10; } } private static void ExitInvalidThreadLoop() { while (true) { ExitInvalidThread(); ExitInvalidThreadLoop_LastRunTime = DateTime.Now; //每10秒检查一次 Thread.Sleep(10 * 1000); } } private static void ExitInvalidThread() { #region 移除不存在的线程(比如线程运行完正常退出终了) Hashtable AllCurrentThreadIDs = new Hashtable(); ProcessThreadCollection ps = Process.GetCurrentProcess().Threads; foreach (ProcessThread t in ps) { AllCurrentThreadIDs.Add(t.Id, null); } int[] IDs = (int[])TaskThreadIDs.Clone(); foreach (int ID in IDs) { if (!AllCurrentThreadIDs.ContainsKey(ID)) TaskThreadIDTable.Remove(ID); } #endregion #region 退出超时的线程 foreach (ProcessThread t in ps) { //跳过非任务线程 if (!TaskThreadIDTable.ContainsKey(t.Id)) continue; //退出超时线程 TimeSpan ts = DateTime.Now.Subtract(t.StartTime); if (ts.TotalMinutes > 10) { try { Thread thread = (Thread)TaskThreadIDTable[t.Id]; thread.Abort(); } catch { } t.Dispose(); } } #endregion } #endregion
Java中,使用foreach循环进行元素remove操作是不安全的,会导致ConcurrentModificationException异常。为了安全地删除元素,应该使用Iterator方式进行remove操作。\[1\]例如,可以使用Iterator的remove方法来删除元素,如下所示: ```java List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if ("2".equals(item)) { iterator.remove(); } } System.out.println(list); ``` 这样就可以安全地删除元素,避免了ConcurrentModificationException异常的发生。\[2\]另外,如果在并发操作中使用Iterator,需要对Iterator对象进行加锁,以确保线程安全性。 #### 引用[.reference_title] - *1* *2* [javaforeach循环进行元素remove/add操作](https://blog.csdn.net/lzxA304/article/details/110084587)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [java中在for循环remove元素时的陷阱](https://blog.csdn.net/lanmuhhh2015/article/details/78413279)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值