remove element of an hashtable in the iteration - java

Is following code the safe way to remove a element in the Hashtable?
Enumeration keys = siCache.keys(); //siCache is Hashtable
while(keys.hasMoreElements())
{
String k = (String) keys.nextElement();
Object v = siCache.get(k);
if(condition) siCache.remove(k);
}

Use the Iterator of the entry set, the key set, or the value set, and call Iterator.remove().

Removing an element from a Hashtable while enumerating the keys is potentially risky. Here's what the javadoc says:
"Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future. The Enumerations returned by Hashtable's keys and elements methods are not fail-fast."
The implication is clear: arbitrary, non-deterministic behaviour is possible if you do that.
Solutions:
If you are using J2SE, use keySet(). Or better still, don't Use Hashtable.
If you are using J2ME, build a list of keys to be removed, and remove them later ... or pray hard :-).

There's a distinct difference between using...
Enumeration keys = siCache.keys();
and using...
Iterator iterator = siCache.entrySet().iterator()
Option 1 will not throw a ConcurrentModificationException when you remove elements in the collection whilst iterating, whereas option 2 will.
As for why... I believe that when you create the keys Enumeration in your example it's a literal copy of the tables key set, which is not kept in sync with modifications to the table itself.
This may or may not be an issue for you. If the table is used concurrently though you may want to switch to using the collections iterators.

It is safe. But what made you think it may not??
tested with the following code.
public static void main(String[] args) {
// TODO Auto-generated method stub
Hashtable siCache = new Hashtable();
siCache.put("key", "value");
siCache.put("key1", "value1");
Enumeration keys = siCache.keys(); //siCache is Hashtable
while(keys.hasMoreElements())
{
String k = (String) keys.nextElement();
Object v = siCache.get(k);
if(true) siCache.remove(k);
}
System.out.println(siCache.size());
}
output : 0

Related

Java: What is the advantage of using HashMap iterator over ConcurrentHashMap?

I have a program that is single-threaded that uses a Map where items are removed one by one while iterating. I have read that iterator can be used here to avoid ConcurrentModificationException but why not use ConcurrentHashMap instead which seems to be much cleaner?
My code:
private final Map<Integer, Row> rowMap;
.....
private void shutDown() {
for (Integer rowNumber : rowMap.keySet()) {
deleteRow(rowNumber)
}
}
....
For my scenario, using a iterator means declaring it final so closeRow() and deleteRow() methods have access to it for removing it. Additionally, the iterator.remove() method does not return the value of the item being removed which is necessary in my case.
My question is, what is the most efficient way to do it so it doesn't throw ConcurrentModificationException? Is it using an iterator or making rowMap a ConcurrentHashMap?
Use ConcurrentHashMap only if it's shared among threads.
In single thread, CurrentModificationException is thrown when the object is modified while an iterator is being used.
There are two ways to remove elements from a collection such as list and map. One is by calling remove on the collection. The other is using an iterator. But they can't be used together. If you remove an element using the remove method of the collection object, it would invalidate the state of the iterator.
List<Integer> list = new ArrayList(List.of(1,2,3,4,5));
Iterator<Integer> it = list.iterator();
list.remove(0);
while(it.hasNext()){
System.out.print(it.next());
}
Here's the exception:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
at Main.main(Main.java:15)
It's a fairly straightforward iterator pattern.
Iterator<Map.Entry<Integer,Row>> it = rowMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer,Row> ent = it.next();
Integer key = ent.getKey();
Row row = ent.getValue(); // before the remove
it.remove();
// ... do what you want with key and row;
}
So, we're iterating through the map with an explicit iterator, which allows us to use the iterator's remove method during iteration. We're iterating over the "entry set" view of the map, which allows us to retrieve both key and value from the single iterator.
Documentation link
public Set<Map.Entry<K,V>> entrySet()
Returns a Set view of the mappings contained in this map. The set is
backed by the map, so changes to the map are reflected in the set, and
vice-versa. If the map is modified while an iteration over the set is
in progress (except through the iterator's own remove operation, or
through the setValue operation on a map entry returned by the
iterator) the results of the iteration are undefined.

Why is removing the 1st entry from a ConcurrentHashMap not immediately reflected in the iterator, but removing the 2nd or subsequent entries is?

I created an iterator() and then removed the 1st entry from the map before iterating it. I always get the 1st item returned from the iterator. But when I remove the 2nd or subsequent entries, the current iterator does not return that entry.
Example of removing 1st entry from map:
Map<Integer,Integer> m1 = new ConcurrentHashMap<>();
m1.put(4, 1);
m1.put(5, 2);
m1.put(6, 3);
Iterator i1 = m1.entrySet().iterator();
m1.remove(4); // remove entry from map
while (i1.hasNext())
System.out.println("value :: "+i1.next()); //still shows entry 4=1
and the output is:
value :: 4=1
value :: 5=2
value :: 6=3
Example of removing 3rd entry from map:
Map<Integer,Integer> m1 = new ConcurrentHashMap<>();
m1.put(4, 1);
m1.put(5, 2);
m1.put(6, 3);
Iterator i1 = m1.entrySet().iterator();
m1.remove(6); // remove entry from map
while (i1.hasNext())
System.out.println("value :: "+i1.next()); //does not show entry 6=3
and the output is:
value :: 4=1
value :: 5=2
Why is removing the 1st entry from the map not reflected in the iterator, but removing the 2nd or subsequent entry is?
The Java documentation says:
Iterators, Spliterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration. They do not throw ConcurrentModificationException.
That means, its iterators reflect the state of the hash table at the point of creation of the iterator.
And when we add or remove an entry to or from the Map, the Iterator will show the original entries?
According to this iterators and spliterators are weakly consistent. The definition of "weakly consistent" can be found here:
Most concurrent Collection implementations (including most Queues)
also differ from the usual java.util conventions in that their
Iterators and Spliterators provide weakly consistent rather than
fast-fail traversal:
they may proceed concurrently with other operations
they will never throw ConcurrentModificationException
they are guaranteed to traverse
elements as they existed upon construction exactly once, and may (but
are not guaranteed to) reflect any modifications subsequent to
construction.
Which means that any modifications made after an iterator has been created may be reflected, but it's not guaranteed. That's just a normal behaviour of a concurrent iterator\spliterator.
To achieve exactly once iteration behavior, when you remove an element via the Iterator object, the iterator data structure would need to be updated to keep it in step with what has happened to the collection. This is not possible in the current implementations because they don't keep links to the outstanding iterators. And if they did, they would need to use Reference objects or risk memory leaks.
The iterator is guaranteed to reflect the state of the map at the time of it's creation. Further changes may be reflected in the iterator, but they do not have to be.
Think Iterator as a LinkedList and you have head reference with you. If you happen to remove the head from linked list and does not reset the head.next value and start iterating from head you still be traversing the from the same head because you are using an outdated head reference. But when you remove non-head elements the prior element.next is updated.
The answer is in the documentation you quoted:
Iterators, Spliterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration.
At OR since. The iterator might or might not show changes to the map since the iterator's creation.
It is not practical for the designers to enforce more precise behavior during concurrent modification and iteration, but it is not broken.
Actual value check is done in next(),
public final Map.Entry<K,V> next() {
Node<K,V> p;
if ((p = next) == null)
throw new NoSuchElementException();
K k = p.key;
V v = p.val;
lastReturned = p;
advance();
return new MapEntry<K,V>(k, v, map);
}
advance method advances if possible, returning next valid node, or null if none.
So for first entry K k = 4; V v = 1; even if it is removed. But for subsequent k,v would decided with update fromadvance()
So if you call next() after remove, it won't be there(which is obvious),
Map<Integer,Integer> m1 = new ConcurrentHashMap<>();
m1.put(4, 1);
m1.put(5, 2);
m1.put(6, 3);
Iterator<Map.Entry<Integer, Integer>> i1 = m1.entrySet().iterator();
m1.remove(4); // remove entry from map
i1.next();
while (i1.hasNext())
System.out.println("value :: "+i1.next());

How come I am not getting "ConcurrentModificationException" while iterating and modifying on HashMap at the same time?

I have a map
Map<String, String> map = new HashMap<String, String>();
map.put("Pujan", "pujan");
map.put("Swati", "swati");
map.put("Manish", "manish");
map.put("Jayant", "jayant");
Iterator<Map.Entry<String, String>> itr = map.entrySet().iterator();
while(itr.hasNext()){
Entry<String,String> entry=(Entry<String, String>) itr.next();
map.put("Manish", "Updated");
}
I don't get an exception here (where I am trying to modify an existing key value "Manish"). But if I try to add a new key map.put("Manish123", "Updated") I get ConcurrentModificationException.
Because your aren't modifying the iterator,
put will mutate an existing entry in this case because a Map.Entry with the same key already exists in the Map.
If you see the Javadoc for the modCount field of a HashMap (in Java 8 HashMap.java source), you will see:
/**
* The number of times this HashMap has been structurally modified.
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
Thus this field keeps the number of times there have been structural modifications to the map. The various iterators in this class throw the ConcurrentModificationException (a better name could have been chosen) when the expected modification count expectedModCount (which is initialized to modCount when you construct this iterator, for example, at the line Iterator<Map.Entry<String, String>> itr = map.entrySet().iterator();) does not match modCount which is mutated any time there are structural modifications to the map (e.g. calling put with a new entry, among other things). Note that different threads are not involved here. All of this can happen in one single thread, for example, when you remove entries from the map or add entries to it while iterating).
As you can now relate, remapping an existing key to a different value should not result in changes to the internal structure of the hash map (since it simply replaces the value associated with the key). And all you are doing is simply remapping the key Manish to a value Updated repeatedly as many times as there are entries in the map (which is 4 and is fixed for the duration of iteration). If, however, added or removed any key you will get the ConcurrentModificationException.
This is analogous to the following code (Note: for illustration purposes only):
List<String> names = Arrays.asList("Larry", "Moe", "Curly");
int i = 0;
Iterator<String> strIter = names.iterator();
while (strIter.hasNext()) {
names.set(i, strIter.next() + " " + i); // value changed, no structural modification to the list
i += 1;
}
System.out.println(names);
which prints:
[Larry 0, Moe 1, Curly 2]
According to Java API : Iterating over collection using Iterator is subject to ConcurrentModificationException if Collection is modified after Iteration started, but this only happens in case of fail-fast Iterators.
There are two types of Iterators in Java, fail-fast and fail-safe, check difference between fail-safe and fail-fast Iterator for more details.
Fail-Fast Iterators in Java
Difference between fail-safe vs fail-fast iterator in javaAs name suggest fail-fast Iterators fail as soon as they realized that structure of Collection has been changed since iteration has begun. Structural changes means adding, removing or updating any element from collection while one thread is Iterating over that collection. fail-fast behavior is implemented by keeping a modification count and if iteration thread realizes the change in modification count it throws ConcurrentModificationException.
Java doc says this is not a guaranteed behavior instead its done of "best effort basis", So application programming can not rely on this behavior. Also since multiple threads are involved while updating and checking modification count and this check is done without synchronization, there is a chance that Iteration thread still sees a stale value and might not be able to detect any change done by parallel threads. Iterators returned by most of JDK1.4 collection are fail-fast including Vector, ArrayList, HashSet etc
Fail-Safe Iterator in java
Contrary to fail-fast Iterator, fail-safe iterator doesn't throw any Exception if Collection is modified structurally
while one thread is Iterating over it because they work on clone of Collection instead of original collection and that’s why they are called as fail-safe iterator. Iterator of CopyOnWriteArrayList is an example of fail-safe Iterator also iterator written by ConcurrentHashMap keySet is also fail-safe iterator and never throw ConcurrentModificationException in Java.

Java ConcurrentHashMap and for each loop

Supposed I have the following ConcurrentHashMap:
ConcurrentHashMap<Integer,String> identificationDocuments = new ConcurrentHashMap<Integer,String>();
identificationDocuments.put(1, "Passport");
identificationDocuments.put(2, "Driver's Licence");
How would I safely iterate over the map with a for each loop and append the value of each entry to a string?
Iterators produced by a ConcurrentHashMap are weakly consistent. That is:
they may proceed concurrently with other operations
they will never throw ConcurrentModificationException
they are guaranteed to traverse elements as they existed upon construction exactly once, and may (but are not guaranteed to) reflect any modifications subsequent to construction.
The last bullet-point is pretty important, an iterator returns a view of the map at some point since the creation of the iterator, to quote a different section of the javadocs for ConcurrentHashMap:
Similarly, Iterators, Spliterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration.
So when you loop through a keyset like the following, you need to double check if the item still exists in the collection:
for(Integer i: indentificationDocuments.keySet()){
// Below line could be a problem, get(i) may not exist anymore but may still be in view of the iterator
// someStringBuilder.append(indentificationDocuments.get(i));
// Next line would work
someStringBuilder.append(identificationDocuments.getOrDefault(i, ""));
}
The act of appending all the strings to the StringBuilder itself is safe, as long as you are doing it on one thread or have encapsulated the StringBuilder entirely in a thread-safe manner.
I don't know If you are really asking this, but to iterate over any map you iterate over the keySet()
StringBuffer result = new StringBuffer("");
for(Integer i: indentificationDocuments.keySet()){
result.append(indentificationDocuments.get(i));
}
return result.toString();

replace all keys in hashmap starting with a character

Set<String> tempSet = new HashSet<String>();
for (Map.Entry<String, String> entry : map.entrySet()) {
String orginalKeys = entry.getKey();
System.out.println(orginalKeys);
String newKey = orginalKeys.replace('.','/');
if (!newKey.equals(orginalKeys)) {
map.put(newKey, entry.getValue());
tempSet.add(orginalKeys);
map.remove(orginalKeys);
}
}
System.out.println(map);
This replaces only one key, but i want to replace all with the above occurrences. What is the wrong thing i am going with?
I see no problem with this code. Assuming, your map has more than one key that matches the critria, e.g. contains at least one dot (.), more than one new keys should have been created with the values taken from the originalkeys and more than one of those originalkeys should have been deleted.
An alternative approach: simply create a new map and drop the old one:
Map<String, String> newMap = new HashMap<String, String>();
for (Map.Entry<String, String> entry : map.entrySet())
newMap.put(entry.getKey().replace('.', '/'), entry.getValue());
map = newMap;
(If you still need that tempSet, modify the code inside the for loop)
This replaces only one key, but i want to replace all with the above occurrences. What is the wrong thing i am going with?
Normally I'd expect your code to give ConcurrentModificationException. The problem is that you are modifying the map while you are iterating it.
The javadoc for HashMap says this:
"The iterators returned by all of this class's "collection view methods" are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future."
"Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs."
If you look at both my solution and #Colin Herbert's solution in the answers to your previous question, you will see that we carefully record what needs to be changed in separate data structures. Only when the iteration is complete do we make the changes to the original Map. This is critical. Without it the solutions don't work.
ideally this code should throw java.util.ConcurrentModificationException as you are iterating through the map.entrySet()) and at the same time updating the map.put(newKey, entry.getValue());
As indicated earlier, you could create a new temporary map and replace with original one
or
you could get the keys in a list and iterate the list
String[] keyArray = map.keySet().toArray(new String[map.size()]);
Set<String> tempSet = new HashSet<String>();
for (String orginalKeys : keyArray) {
System.out.println(orginalKeys);
String newKey = orginalKeys.replace('.', '/');
if (!newKey.equals(orginalKeys)) {
map.put(newKey, map.get(orginalKeys));
tempSet.add(orginalKeys);
map.remove(orginalKeys);
}
}
System.out.println(map);
I'm not sure whether you can modify a map while iterating over it. Try collecting the entries to change in a list first, then later looping over that list to make the changes.

Categories

Resources