Java遍历HashMap并修改(remove)
遍历HashMap的方法有多种,比如通过获取map的keySet, entrySet, iterator之后,都可以实现遍历,然而如果在遍历过程中对map进行读取之外的操作则需要注意使用的遍历方式和操作方法。
private static Map<Integer, String> map = new HashMap<Integer, String>();
public static void main(String[] args) {
//init
for(int i = 0; i < 10; i++){
map.put(i, "value" + i);
}
for(Map.Entry<Integer, String> entry : map.entrySet()){
Integer key = entry.getKey();
if(key % 2 == 0){
System.out.println("To delete key " + key);
map.remove(key);
System.out.println("The key " + + key + " was deleted");
}
}
System.out.println("map size = " + map.size());
for(Map.Entry<Integer, String> entry : map.entrySet()){
System.out.println( entry.getKey() +" = " + entry.getValue());
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class MapIteratorTest {
private static Map<Integer, String> map = new HashMap<Integer, String>();
public static void main(String[] args) {
//init
for(int i = 0; i < 10; i++){
map.put(i, "value" + i);
}
for(Map.Entry<Integer, String> entry : map.entrySet()){
Integer key = entry.getKey();
if(key % 2 == 0){
System.out.println("To delete key " + key);
map.remove(key);
System.out.println("The key " + + key + " was deleted");
}
}
System.out.println("map size = " + map.size());
for(Map.Entry<Integer, String> entry : map.entrySet()){
System.out.println( entry.getKey() +" = " + entry.getValue());
}
}
}
|
上面代码的输出结果为
The key 0 was deleted
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$EntryIterator.next(HashMap.java:834)
at java.util.HashMap$EntryIterator.next(HashMap.java:832)
at com.gpzuestc.collection.MapIteratorTest.main(MapIteratorTest.java:60)
1
2
3
4
5
6
7
|
To delete key 0
The key 0 was deleted
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$EntryIterator.next(HashMap.java:834)
at java.util.HashMap$EntryIterator.next(HashMap.java:832)
at com.gpzuestc.collection.MapIteratorTest.main(MapIteratorTest.java:60)
|
通过上面的输出可以发现第一个偶数key元素已经被成功remove,异常的抛出位置是在迭代器遍历下一个元素的时候。
如果把上面高亮的遍历代码替换成keySet的方式,通过keySet的remove操作同样会在遍历下个元素时抛出异常,示例如下。
for(Integer key : keySet){
if(key % 2 == 0){
System.out.println("To delete key " + key);
keySet.remove(key);
System.out.println("The key " + + key + " was deleted");
}
}
1
2
3
4
5
6
7
8
|
Set<Integer> keySet = map.keySet();
for(Integer key : keySet){
if(key % 2 == 0){
System.out.println("To delete key " + key);
keySet.remove(key);
System.out.println("The key " + + key + " was deleted");
}
}
|
The key 0 was deleted
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
at com.gpzuestc.collection.MapIteratorTest.main(MapIteratorTest.java:49)
1
2
3
4
5
6
|
To delete key 0
The key 0 was deleted
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
at com.gpzuestc.collection.MapIteratorTest.main(MapIteratorTest.java:49)
|
如果要实现遍历过程中进行remove操作,上面两种方式都不能使用,而是需要通过显示获取keySet或entrySet的iterator来实现。
while(it.hasNext()){
Map.Entry<Integer, String> entry = it.next();
Integer key = entry.getKey();
if(key % 2 == 0){
System.out.println("To delete key " + key);
it.remove();
System.out.println("The key " + + key + " was deleted");
}
}
1
2
3
4
5
6
7
8
9
10
11
|
Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
while(it.hasNext()){
Map.Entry<Integer, String> entry = it.next();
Integer key = entry.getKey();
if(key % 2 == 0){
System.out.println("To delete key " + key);
it.remove();
System.out.println("The key " + + key + " was deleted");
}
}
|
The key 0 was deleted
To delete key 2
The key 2 was deleted
To delete key 4
The key 4 was deleted
To delete key 6
The key 6 was deleted
To delete key 8
The key 8 was deleted
map size = 5
1 = value1
3 = value3
5 = value5
7 = value7
9 = value9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
To delete key 0
The key 0 was deleted
To delete key 2
The key 2 was deleted
To delete key 4
The key 4 was deleted
To delete key 6
The key 6 was deleted
To delete key 8
The key 8 was deleted
map size = 5
1 = value1
3 = value3
5 = value5
7 = value7
9 = value9
|
分析原因
其实上面的三种遍历方式从根本上讲都是使用的迭代器,之所以出现不同的结果是由于remove操作的实现不同决定的。
首先前两种方法都在调用nextEntry方法的同一个地方抛出了异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
...
...
}
1
2
3
4
5
6
7
|
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
...
...
}
|
这里modCount是表示map中的元素被修改了几次(在移除,新加元素时此值都会自增),而expectedModCount是表示期望的修改次数,在迭代器构造的时候这两个值是相等,如果在遍历过程中这两个值出现了不同步就会抛出ConcurrentModificationException异常。
1、HashMap的remove方法实现
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
1
2
3
4
|
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
|
2、HashMap.KeySet的remove方法实现
return HashMap.this.removeEntryForKey(o) != null;
}
1
2
3
|
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
|
3、HashMap.HashIterator的remove方法实现
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
1
2
3
4
5
6
7
8
9
10
|
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
|
以上三种实现方式都通过调用HashMap.removeEntryForKey方法来实现删除key的操作。在removeEntryForKey方法内只要移除了key modCount就会执行一次自增操作,此时modCount就与expectedModCount不一致了,上面三种remove实现中,只有第三种iterator的remove方法在调用完removeEntryForKey方法后同步了expectedModCount值与modCount相同,所以在遍历下个元素调用nextEntry方法时,iterator方式不会抛异常。
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
final Entry<K,V> removeEntryForKey(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
|
发散
1、如果是遍历过程中增加或修改数据呢?
增加或修改数据只能通过Map的put方法实现,在遍历过程中修改数据可以,但如果增加新key就会在下次循环时抛异常,因为在添加新key时modCount也会自增。
2、有些集合类也有同样的遍历问题,如ArrayList,通过Iterator方式可正确遍历完成remove操作,直接调用list的remove方法就会抛异常。
for(String str : list){
list.remove(str);
}
//正确遍历移除方式
Iterator<String> it = list.iterator();
while(it.hasNext()){
it.next();
it.remove();
}
1
2
3
4
5
6
7
8
9
10
11
|
//会抛ConcurrentModificationException异常
for(String str : list){
list.remove(str);
}
//正确遍历移除方式
Iterator<String> it = list.iterator();
while(it.hasNext()){
it.next();
it.remove();
}
|
3、jdk为什么这样设计,只允许通过iterator进行remove操作?
HashMap和keySet的remove方法都可以通过传递key参数删除任意的元素,而iterator只能删除当前元素(current),一旦删除的元素是iterator对象中next所正在引用的,如果没有通过modCount、 expectedModCount的比较实现快速失败抛出异常,下次循环该元素将成为current指向,此时iterator就遍历了一个已移除的过期数据。
Java遍历HashMap并修改(remove)的更多相关文章
- Java遍历HashMap并修改(remove)(转载)
遍历HashMap的方法有多种,比如通过获取map的keySet, entrySet, iterator之后,都可以实现遍历,然而如果在遍历过程中对map进行读取之外的操作则需要注意使用的遍历方式和操 ...
- java遍历Hashmap/Hashtable的几种方法
一>java遍历Hashtabe: import java.util.Hashtable; import java.util.Set; public class HashTableTest { ...
- java遍历hashMap、hashSet、Hashtable
一.遍历HashMap Map<Integer, String> map = new HashMap<Integer, String>(); 方法一:效率高 for(Entry ...
- [Java] 遍历HashMap和HashMap转换成List的两种方式
遍历HashMap和HashMap转换成List /** * convert the map to the list(1) */ public static void main(String[] ...
- JAVA遍历HashMap和ArrayList
List Map 基础信息 HashMap 最近写程序经常需要遍历集合,所以总结一下内容: 一.简单实现 Map map = new HashMap(); for(Object o : map.key ...
- java遍历HashMap的高效方法
https://stackoverflow.com/questions/46898/how-do-i-efficiently-iterate-over-each-entry-in-a-java-map
- java 中 HashMap 遍历与删除
HashMap的遍历 方法一.这是最常见的并且在大多数情况下也是最可取的遍历方式 /** * 在键值都需要时使用 */ Map<Integer, Integer> map = new Ha ...
- Java循环遍历中直接修改遍历对象
Java 循环遍历中直接修改遍历对象如下,会报异常: for (ShopBaseInfo sp: sourceList) { if(sp.getId()==5){ sourceList.remove( ...
- java 中遍历hashmap 和hashset 的方法
一.java中遍历hashmap: for (Map.Entry<String, Integer> entry : tempMap.entrySet()) { String ...
随机推荐
- poj 1094 Sorting It All Out 解题报告
题目链接:http://poj.org/problem?id=1094 题目意思:给出 n 个待排序的字母 和 m 种关系,问需要读到第 几 行可以确定这些字母的排列顺序或者有矛盾的地方,又或者虽然具 ...
- 后缀自动机SAM BZOJ 2806
终于遇到了一道后缀数组不能过 一定要学SAM的题... (看了半个下午+半个上午) 现在总结一下(是给我自己总结..所以只总结了我觉得重要的 .. 看不太懂的话可以To http://blog.c ...
- python(一):multiprocessing——死锁
前言近年来,使用python的人越来越多,这得益于其清晰的语法.低廉的入门代价等因素.尽管python受到的关注日益增多,但python并非完美,例如被人诟病最多的GIL(值得注意的是,GIL并非py ...
- 通过链接将JSP页面中一变量传到另一JSP界面中
A.jsp 发送 <a herf="B.jsp?name=<%=name%>">传递到B页面</a> B.jsp 接收 <%String ...
- ubuntu scp命令或者用root连接ssh提示:Permission denied, please try again.错误
1.su - #!!! 2.vi /etc/ssh/sshd_config 3.PermitRootLogin yes # 找到此字段,改为此行所示 4./etc/init.d/ssh restart ...
- JAVA基础--流程控制语句02
JAVA流程控制语句 一.程序流程 定义:只代码执行的先后顺序 种类 顺序流程:程序默认流程,按照书写顺序执行 选择流程:选择语句 判断语句 重复流程: 循环控制语句 二.选择流程控制语句 判断语句 ...
- mq4参考
更新: 2017/05/24 其实也就是照搬文档,主要是用到一个记一个.方便掌握 ----------------------------------------------------------- ...
- 20170406-ms
Interval 间隔 revoke v撤销 alert adj 警觉的 n警报
- [转载]OI省选算法汇总
简单列了一点 1.1 基本数据结构 数组 链表,双向链表 队列,单调队列,双端队列 栈,单调栈 1.2 中级数据结构 堆 并查集与带权并查集 hash 表 自然溢出 双hash 1.3 高级数据结构 ...
- N的阶乘HDOJ1042
我记得有一份代码是非常有技巧的,然而这一份就是很死板-每次跑50000,因为10000的阶乘最多才50000位,这样肯定就过了 #include<cstdio> #include<s ...