Java遍历HashMap并修改(remove)(转载)
Java遍历HashMap并修改(remove)(转载)
遍历HashMap的⽅法有多种,⽐如通过获取map的keySet, entrySet, iterator之后,都可以实现遍历,然⽽如果在遍历过程中对map进⾏读取之外的操作则需要注意使⽤的遍历⽅式和操作⽅法。
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 : Set()){
Integer key = Key();
if(key % 2 == 0){
System.out.println("To delete key " + key);
System.out.println("The key " + + key + " was deleted");
}
}
System.out.println("map size = " + map.size());
for(Map.Entry<Integer, String> entry : Set()){
System.out.println( Key() +" = " + Value());
}
}
}
上⾯代码的输出结果为
To delete key 0
The key 0 was deleted
Exception in thread "main" java.util.ConcurrentModificationException
镇江培训网at java.util.Entry(HashMap.java:793)
at java.util.(HashMap.java:834)
at java.util.(HashMap.java:832)
at llection.MapIteratorTest.main(MapIteratorTest.java:60)
通过上⾯的输出可以发现第⼀个偶数key元素已经被成功remove,异常的抛出位置是在迭代器遍历下⼀个元素的时候。
如果把上⾯⾼亮的遍历代码替换成keySet的⽅式,通过keySet的remove操作同样会在遍历下个元素时抛出异常,⽰例如下。
造型英文Set<Integer> keySet = map.keySet();
for(Integer key : keySet){
if(key % 2 == 0){
System.out.println("To delete key " + key);
System.out.println("The key " + + key + " was deleted");
}
}
To delete key 0
The key 0 was deleted
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.Entry(HashMap.java:793)
at java.util.(HashMap.java:828)
at llection.MapIteratorTest.main(MapIteratorTest.java:49)
如果要实现遍历过程中进⾏remove操作,上⾯两种⽅式都不能使⽤,⽽是需要通过显⽰获取keySet或entrySet的iterator来实现。
Iterator<Map.Entry<Integer, String>> it = Set().iterator();
while(it.hasNext()){
Map.Entry<Integer, String> entry = it.next();
Integer key = Key();
if(key % 2 == 0){
System.out.println("To delete key " + key);
System.out.println("The key " + + key + " was deleted");
}
}
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
outrageous5 = value5
7 = value7
聚餐英文9 = value9
hwp
分析原因
其实上⾯的三种遍历⽅式从根本上讲都是使⽤的迭代器,之所以出现不同的结果是由于remove操作的实现不同决定的。
⾸先前两种⽅法都在调⽤nextEntry⽅法的同⼀个地⽅抛出了异常
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
...
...
}
这⾥modCount是表⽰map中的元素被修改了⼏次(在移除,新加元素时此值都会⾃增),⽽expectedModCount是表⽰期望的修改次数,在迭代器构造的时候这两个值是相等,如果在遍历过程中这两个值出现了不同步就会抛出ConcurrentModificationException异常。
1、HashMap的remove⽅法实现
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
2、HashMap.KeySet的remove⽅法实现
public boolean remove(Object o) {
return veEntryForKey(o) != null;
}
3、HashMap.HashIterator的remove⽅法实现
public void remove() {
if (current == null)
throw new IllegalStateException();
sonya
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
veEntryForKey(k);
expectedModCount = modCount;
}
以上三种实现⽅式都通过调⽤veEntryForKey⽅法来实现删除key的操作。在removeEntryForKey⽅法内只要移除了key modCount就会执⾏⼀次⾃增操作,此时modCount就与expectedModCount不⼀致了,上⾯三种remove实现中,只有第三种iterator 的remove⽅法在调⽤完removeEntryForKey⽅法后同步了expectedModCount值与modCount相同,所以在遍历下个元素调⽤nextEntry⽅法时,iterator⽅式不会抛异常。
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;
el
< = next;
return e;
}
prev = e;
e = next;
}
return e;
}
发散
1、如果是遍历过程中增加或修改数据呢?
增加或修改数据只能通过Map的put⽅法实现,在遍历过程中修改数据可以,但如果增加新key就会在下次循环时抛异常,因为在添加新key 时modCount也会⾃增。
2、有些集合类也有同样的遍历问题,如ArrayList,通过Iterator⽅式可正确遍历完成remove操作,直接调⽤list的remove⽅法就会抛异常。
//会抛ConcurrentModificationException异常
for(String str : list){
}
//正确遍历移除⽅式
Iterator<String> it = list.iterator();
while(it.hasNext()){
<();matured
}
3、jdk为什么这样设计,只允许通过iterator进⾏remove操作?
HashMap和keySet的remove⽅法都可以通过传递key参数删除任意的元素,⽽iterator只能删除当前元素(current),⼀旦删除的元素是iterator对象中next所正在引⽤的,如果没有通过modCount、 expectedModCount的⽐较实现快速失败抛出异常,下次循环该元素将成为current指向,此时iterator就遍历了⼀个已移除的过期数据。
def