There are many ways to traverse HashMap, for example, by acquiring map keyset, EntrySet, iterator, and so on, however, if you do not read the map in the traversal process, you need to pay attention to the use of the traversal mode and operation method.
public class Mapiteratortest { private static Map<integer, string> Map = new Hashmap<integer, string> (); C1/>public static void Main (string[] args) { //init for (int i = 0; i <; 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 output of the above code is
To delete key 0The key 0 is deletedexception in thread "main" Java.util.ConcurrentModificationExceptionat Java.util.HashM Ap$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)
The above output can be used to find that the first even key element has been successfully removed, and the exception is thrown at the time the iterator traverses the next element.
If you replace the above highlighted traversal code with keyset, the remove operation through keyset also throws an exception when traversing the next element, such as the following.
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"); } }
To delete key 0The key 0 is deletedexception in thread "main" Java.util.ConcurrentModificationExceptionat Java.util.HashM Ap$hashiterator.nextentry (hashmap.java:793) at Java.util.hashmap$keyiterator.next (HashMap.java:828) at Com.gpzuestc.collection.MapIteratorTest.main (mapiteratortest.java:49)
If you want to implement a remove operation during traversal, neither of the above can be used, but it needs to be achieved by displaying a iterator that gets keyset or entryset.
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"); } }
To delete key 0The key 0 is deletedto delete key 2The key 2 was deletedto delete key 4The key 4 is deletedto delete key 6The Key 6 was deletedto delete key 8The key 8 is deletedmap size = Wuyi = Value13 = value35 = Value57 = Value79 = Value9
Analysis Reason
In fact, the above three traversal methods are basically used iterators, the reason for the different results is due to the implementation of the remove operation different decision.
First both methods throw an exception in the same place where the NextEntry method is called
Final entry<k,v> NextEntry () { if (modcount! = expectedmodcount) throw new Concurrentmodificationexception (); Entry<k,v> e = next; ... ... }
The modcount here is to indicate that the elements in the map have been modified several times (this value will increment when new elements are removed), whereas Expectedmodcount is the desired number of modifications, which are equal at the time of construction of the iterator. If these two values appear out of sync during the traversal, the concurrentmodificationexception exception is thrown.
1, HashMap the Remove method implementation
Public V Remove (Object key) { entry<k,v> e = Removeentryforkey (key); return (E = = null? null:e.value);}
2, Hashmap.keyset the Remove method implementation
public boolean remove (Object o) { return HashMap.this.removeEntryForKey (o) = null;}
3, Hashmap.hashiterator the Remove method implementation
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;}
The above three implementations are implemented by calling the Hashmap.removeentryforkey method to remove key. In the Removeentryforkey method, as long as you remove the key Modcount will perform a self-increment operation, at this time Modcount and expectedmodcount inconsistent, the above three remove implementations, Only the third iterator remove method synchronizes the Expectedmodcount value with Modcount after the Removeentryforkey method is called, so when the next element is traversed to invoke the NextEntry method, The iterator method does not throw an exception.
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; }
Divergence
1. What if the data is added or modified during traversal?
Adding or modifying data can only be done through the put method of the map, modifying the data during traversal, but adding a new key will throw an exception at the next loop, because Modcount will also increment when the new key is added.
2, some collection classes also have the same traversal problem, such as ArrayList, through the iterator way can be properly traversed to complete the remove operation, directly call the list of the Remove method will throw an exception.
Will throw Concurrentmodificationexception exception for (String str:list) {list.remove (str);}//Correct traversal removal method iterator<string> it = List.iterator (); while (It.hasnext ()) {it.next (); It.remove ();}
3. Why is the JDK designed so that only the remove operation is allowed through iterator?
The Remove method of both HashMap and keyset can remove any element by passing the key parameter, and iterator can only delete the current element, once the deleted element is the iterator object in which next is being referenced, If you fail to throw an exception by Modcount, Expectedmodcount comparison, the next loop will be the current point, and iterator will traverse a removed expired data.
Java Traversal HashMap and modify (remove) (reprint)