This question already has answers here:
Changing HashMap keys during iteration
(5 answers)
Closed 8 years ago.
I have took below failures in my trace when trying to run the application. I do not undertand the reason behind this error. Is it result from static keyword, or is one thread trying to modify something in this code segment? Importantly, how can I solve this error?
Failure Trace
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
at java.util.HashMap$KeyIterator.next(Unknown Source)
The Code segment
// Type of holder is --> HashMap<Integer, HashMap<String, Integer>>
Set<Integer> keys = holder.keySet();
HashMap<String, Integer> temp = new HashMap<String, Integer>();
for(int iter : keys){
temp = holder.get(iter);
if(temp == null || temp.size() == 0){
holder.remove(iter);
}
}
Should I use lock around some statement or all of them? Not knowing the real problem restrict to find a solution. Anyway, thanks
I don't think you can call holder.remove() while you are iterating the holder keys. Instead, you can do it with one something like
HashMap<String, Integer> temp = null; // <-- why create one?
List<Integer> toRemove = new ArrayList<>();
for(int iter : keys) {
temp = holder.get(iter);
if (temp == null || temp.size() == 0) {
toRemove.add(iter);
}
}
keys.removeAll(toRemove);
Or, per the HashMap and LinkedHashMap Javadocs -
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.
You can't change the contents of an Iterable while iterating over it. Instead you could store the relevant values in a list and then remove them after the loop has finished.
the call to holder.remove() causes the HashMap to change its contents which in turn changes its keyset that you are using for the loop, thus causing the exception.
Related
I have some code:
Map<String, Integer> letters = new HashMap<String, Integer>();
letters.put(String.valueOf(input.charAt(0)),
numberOfLettersInWord(input,input.charAt(0)));
for (int i = 0; i < input.length(); i++) {
for (String key : letters.keySet()) {
if (!letters.containsKey(String.valueOf(input.charAt(i)))) {
letters.put(String.valueOf(input.charAt(i)),
numberOfLettersInWord(input,input.charAt(i)));
} else continue;
System.out.println(letters);
}
System.out.println(1);
}
System.out.println(2);
The main idea in the code - there is some word in String input(not empty, not null, with no non-letter symbols), need to count how many times each letter can be found there. Counting works OK (in the numberOfLettersInWord method) but when I try to add letters and digits to HashMap<String, Integer> some problems happens - it adds all letters and their numbers correctly but some error pops up. For this code it will show:
1
1
{a=4, b=4}
1
1
1
1
{a=4, b=4, c=3}
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1579)
at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1602)
at LetterCounter.count(LetterCounter.java:25)
at LetterCounter.main(LetterCounter.java:11)
Process finished with exit code 1
From what I see there is something happens when there are no new letters to be added. Can you explain why this happens and how to solve this?
It supposed to have some more digit outputs after the {a=4, b=4, c=3} was shown but it ends with the exception (it is not really necessary, just an indicator where it stops working...)
The word used in this run was String input = "aabbabcccba";
numberOfLettersInWord returns Integer value of how many times letter input.charAt(i) was found in word input(this works ok)
line 2 in code fragment was used just to make the HashMap contain at least one line (null and empty checks already done by this moment and work well)
I saw people had problems with hashmap.remove() in Why is a ConcurrentModificationException thrown and how to debug it but I am not sure this is the same-same thing that can be solved with that answer.
Also I am not sure this answer is applicable for my case ConcurrentModificationException - HashMap
ok, i think i solved it:
Map<String, Integer> letters = new HashMap<String, Integer>();
letters.put(String.valueOf(input.charAt(0)),numberOfLettersInWord(input,input.charAt(0)));
for(int i = 0; i < input.length(); i++) {
letters.putIfAbsent(String.valueOf(input.charAt(i)),numberOfLettersInWord(input,input.charAt(i)));
}
i removed some extra code and it started work, even all tests passed
Why the ConcurrentModificationException?
You're getting a ConcurrentModificationException because you are structurally modifying the map while iterating its key set.
Documentation
Here's what the documentation of HashMap says on the subject:
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.
Those "collection view methods" it mentions are the following:
HashMap#keySet(), which returns a Set<K>.
HashMap#values(), which returns a Collection<V>.
HashMap#entrySet(), which returns a Set<Map.Entry<K, V>>.
For-Each Loops
If you aren't aware, a for-each loop uses an Iterator behind the scenes. In other words, something like this:
List<String> list = List.of("one", "two", "three");
for (String element : list) {
System.out.println(element);
}
Is compiled down to:
List<String> list = List.of("one", "two", "three");
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
String element = iterator.next();
System.out.println(element);
}
Your Code
You have a for-each loop iterating over the key set of your map. Inside this for-each loop you have a call to put, which is a structurally-modifying operation, on the same map.
for (String key : letters.keySet()) {
if (!letters.containsKey(String.valueOf(input.charAt(i)))) {
letters.put(String.valueOf(input.charAt(i)),
numberOfLettersInWord(input,input.charAt(i)));
} else continue;
System.out.println(letters);
}
Thus, a ConcurrentModificationException is likely to be thrown. In your case it's all but guaranteed.
Solution
You are apparently trying to count the frequencies of each letter in a string. This does not require you to loop over the key set of the map. The fact you don't actually use the key variable anywhere inside the for-each loop is a good indicator of this. This means you can simply get rid of the for-each loop and your code should work just fine.
Map<String, Integer> letters = new HashMap<String, Integer>();
letters.put(String.valueOf(input.charAt(0)), numberOfLettersInWord(input,input.charAt(0)));
for (int i = 0; i < input.length(); i++) {
if (!letters.containsKey(String.valueOf(input.charAt(i)))) {
letters.put(String.valueOf(input.charAt(i)), numberOfLettersInWord(input,input.charAt(i)));
}
}
Note that call to put if the map does not already contain the key could be replaced with a call to computeIfAbsent. That method takes the key and a Function that computes the value if the key is not already contained in the map (or if the key is currently mapped to null). It would look something like this:
Map<String, Integer> letters = new HashMap<String, Integer>();
letters.put(String.valueOf(input.charAt(0)), numberOfLettersInWord(input,input.charAt(0)));
for (int i = 0; i < input.length(); i++) {
letters.computeIfAbsent(String.valueOf(input.charAt(i)), key -> numberOfLettersInWord(input, key));
}
Note: The second argument the above computeIfAbsent call is a Function implemented via a lambda expression.
Potential Improvements
There may be a couple of improvements you could make to your code.
Change Key Type to Character
Given you're counting the frequency of characters, it would make sense to represent that in the code by using a Map<Character, Integer> instead of a Map<String, Integer>.
Count as You Go
I can only assume that numberOfLettersInWord loops over the input string and counts how many times the given character occurs in said string. This means you loop over the string for each character in the string, resulting in an inefficient algorithm. Though you do have optimization where you only compute the frequency of a character if you haven't already done so for that character, so that improves things a little.
However, you're already looping over all the characters in the input string. You might as well count the frequency of each character as you go. It could look something like:
String input = ...;
Map<Character, Integer> frequencies = new HashMap<>();
for (int i = 0; i < input.length(); i++) {
Character key = input.charAt(i);
Integer value = frequencies.get(key);
if (value == null) {
frequencies.put(key, 1);
} else {
frequencies.put(key, value + 1);
}
}
Use compute to Count
The body of that for loop can be replaced with a call to compute:
String input = ...;
Map<Character, Integer> frequencies = new HashMap<>();
for (int i = 0; i < input.length(); i++) {
frequencies.compute(input.charAt(i), (key, value) -> {
if (value == null) {
return 1;
} else {
return value + 1;
}
});
}
And that lambda expression (implementing a BiFunction) can be "simplified" even more:
(key, value) -> value == null ? 1 : value + 1
Use merge to Count
Another option is to use merge:
frequencies.merge(input.charAt(i), 1, Integer::sum);
Note: The Integer::sum is a method reference implementing a BiFunction.
letters.keySet() is returning a set which is backed by the keys of the HashMap, which you then alter by calling put(). So the conflict here is between the keySet and the keys of the map. You would need to use an iterator, or extract the keys into a separate collection, by copying the keySet each time through the outer loop. Honestly, the algorithm is sounding kind of expensive, though I haven't really tried to work out a better approach...
This question already has answers here:
Iterating through a Collection, avoiding ConcurrentModificationException when removing objects in a loop
(31 answers)
Closed 6 years ago.
The runtime indicates the exception occurs when giving temp = keysit.next(). I thought this had been taken care of when I redefined keysit = keys.iterator() for the 2nd time, but maybe I'm missing the point. Any suggestions?
Map<Integer, Set<String>> lhm = new LinkedHashMap<Integer, Set<String>>();
public void sortMap() {
Set<Integer> keys = hm.keySet();
Iterator<Integer> keysit;
int iterations = keys.size();
int smallest;
int temp;
for(int i=0; i<iterations; i++) {
keysit = keys.iterator();
smallest = keysit.next();
keysit = keys.iterator();
while(keysit.hasNext()) {
temp = keysit.next();
if(temp<smallest)
smallest = temp;
lhm.put(smallest, lhm.get(smallest));
keys.remove(smallest);
}
}
System.out.println(lhm);
}
The point is iterator maintains an integer flag named - modCount which keep track of modifications during iteration.
In following line of code
keys.remove(smallest);
you are actually removing element from the set which changes this modcount. So next time when next() is called to get the next element, it checks whether modcount value has been changed or not. If yes then throw concurrent modification exception.
So all in all the modification is dependent on the modcount flag and doesn't depend on how many times you redefine keys.iterator().
One good option is to use ConcurrentHashMap as suggested by #Olu
Use a Concurrenthashmap instead of a hashmap or map bcoz hashmap is not threadsafe
All,
Running into the issue of ConcurrentModificationException and struggling to find a resolution partly because I can't see where I am modifiying the list while iterating it... Any ideas?? I've highlighted the line that is causing the issue (it3.remove()). Really at a standstill with this one..
EDIT: Stacktrace:
Exception in thread "Thread-4" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at com.shimmerresearch.advancepc.InternalFrameAndPlotManager.subtractMaps(InternalFrameAndPlotManager.java:1621)
Line 1621 corresponds to it3.remove() in my referenced code above.
private void subtractMaps(ConcurrentSkipListMap<String, PlotDeviceDetails> firstMap, ConcurrentSkipListMap<String, PlotDeviceDetails> secondMap) {
// iterate through the secondmap
Iterator<Entry<String, PlotDeviceDetails>> it1 = secondMap.entrySet().iterator();
while (it1.hasNext()) {
Entry<String, PlotDeviceDetails> itEntry = (Entry) it1.next();
String mapKey = (String) it1Entry.getKey();
PlotDeviceDetails plotDeviceDetails = (PlotDeviceDetails)it1Entry.getValue();
// if we find an entry that exists in the second map and not in the first map continue
if(!firstMap.containsKey(mapKey)){
continue;
}
// iterate through a list of channels belonging to the secondmap
Iterator <PlotChannelDetails> it2 = plotDeviceDetails.mListOfPlotChannelDetails.iterator();
while (it2.hasNext()) {
PlotChannelDetails it2Entry = it2.next();
// iterate through a list of channels belonging to the firstmap
Iterator <PlotChannelDetails> it3 = firstMap.get(mapKey).mListOfPlotChannelDetails.iterator();
innerloop:
while(it3.hasNext()){
// if a channel is common to both first map and second map, remove it from firstmap
PlotChannelDetails it3Entry = it3.next();
if(it3Entry.mChannelDetails.mObjectClusterName.equals(it2Entry.mChannelDetails.mObjectClusterName)){
it3.remove(); // this line is causing a concurrentModificationException
break innerloop;
}
}
}
}
}
plotDeviceDetails.mListOfPlotChannelDetails and firstMap.get(mapKey).mListOfPlotChannelDetails reference the same list.
Whether plotDeviceDetails and firstMap.get(mapKey) also reference the same object is unknown without more information, but they share the channel list.
The stack trace is showing that mListOfPlotChannelDetails is an ArrayList, and since the stack trace also shows that the error is coming from it3.remove(), and there is nothing in the code that can cause that, then you are truly facing a concurrent modification, i.e. another thread has updated the ArrayList being iterated by it3.
Remember, ArrayList does not support concurrent multi-thread access.
I keep getting a concurrent modification exception on my code. I'm simply iterating through a hashmap and modifying values. From researching this I found people said to use iterators and iterator.remove, etc. I tried implementing with this and still kept getting the error. I thought maybe multiple threads accessed it? (Although in my code this block is only run in one thread) So I put it in a synchronized block. However, I'm still getting the error.....
Map map= Collections.synchronizedMap(questionNumberAnswerCache);
synchronized (map) {
for (Iterator<Map.Entry<String, Integer>> it = questionNumberAnswerCache.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Integer> entry = it.next();
if (entry.getKey() == null || entry.getValue() == null) {
continue;
} else {
try {
Question me = Question.getQuery().get(entry.getKey());
int i = Activity.getQuery()
.whereGreaterThan(Constants.kQollegeActivityCreatedAtKey, lastUpdated.get("AnswerNumberCache " + entry.getKey()))
.whereEqualTo(Constants.kQollegeActivityTypeKey, Constants.kQollegeActivityTypeAnswer)
.whereEqualTo(Constants.kQollegeActivityQuestionKey, me)
.find().size();
lastUpdated.put("AnswerNumberCache " + entry.getKey(), Calendar.getInstance().getTime());
int old_num = entry.getValue();
entry.setValue(i + old_num);
} catch (ParseException e) {
entry.setValue(0);
}
}
}
}
Error:
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:787)
at java.util.HashMap$EntryIterator.next(HashMap.java:824)
at java.util.HashMap$EntryIterator.next(HashMap.java:822)
at com.juryroom.qollege_android_v1.QollegeCache.refreshQuestionAnswerNumberCache(QollegeCache.java:379)
at com.juryroom.qollege_android_v1.QollegeCache.refreshQuestionCaches(QollegeCache.java:267)
at com.juryroom.qollege_android_v1.UpdateCacheService.onHandleIntent(UpdateCacheService.java:28)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.os.HandlerThread.run(HandlerThread.java:61)
What is happening:
The iterator is looping through the map. The map isn't really like a list, because it doesn't care about order. So when you add something to the map it might get inserted into the middle, somewhere in the middle of the objects you already looped through, at the end, etc. So instead of giving you random behavior it fails.
Your solutions:
Synchronized map and synchronized blocks allow you to have two threads going at it at the same time. It doesn't really help here, since the problem is that the same thread is modifying it in an illegal manner.
What you should do:
You could just save the keys you want to modify. Making a map with keys and new values won't be a problem unless this is a really time critical piece of code.
Then you just iterate through the newValues map and update the oldValues map. Since you are not iterating through the map being updated it's not a problem.
Or you could simply iterate just through the keys (for String s : yourMap) and then look up the values you want to change. Since you are just iterating through the keys you are free to change the values (but you can't remove values).
You could also try to use a ConcurrentHashMap which should allow you to modify it, but the behavior is undefined so this is risky. Just changing values shouldn't lead to problems, but if you add or remove you never know if it will end up being iterated through or not.
Create an object, and is locked to it - a good way to shoot yourself in the foot.
I recommend the following code to remove the hash map.
HashMap<Key, Object> hashMap = new HashMap<>();
LinkedList<Key> listToRemove = new LinkedList<>();
for(Map.Entry<Key, Object> s : hashMap.entrySet()) {
if(s.getValue().equals("ToDelete")){
listToRemove.add(s.getKey());
}
}
for(Key s : listToRemove) {
hashMap.remove(s);
}
It's not the most beautiful and fastest option, but it should help you to understand how to work with HashMap.
As you will understand how to work my option. You can learn how to work iterators, how to work iterators in loop. (rather than simply copy-paste)
Iterator it = tokenMap.keySet())
while(it.hasNext()) {
if(/* some condition */) it.remove();
}
I would suggest the following for your use case:
for(Key key : hashMap.keySet()) {
Object value = hashMap.get(key);
if(<condition>){
hashMap.put(key, <new value>);
}
If you are not deleting any entries and just changing the value, this should work for you.
This question already has answers here:
Calling remove in foreach loop in Java [duplicate]
(11 answers)
Closed 9 years ago.
I am trying to create a huffman tree, and am in the middle of attempting to merge two trees. I can not figure out how to remove a Tree in my program without getting the "concurrent Modification Exception" because I am iterating over a list and attempting to remove from the list at the same time.
BinaryTree<Character, Integer> t1 = null;
BinaryTree<Character, Integer> t2 = null;
BinaryTree<Character, Integer> tFinal = null;
int treeSize = TREES.size();
for (int i = 0; i < treeSize; i++) {
for (BinaryTree<Character, Integer> t : TREES) {
System.out.println("treeSize " + treeSize);
System.out.println(t.getRoot().getElement()
+ " t.getRoot().getElement()");
// here I edited the merge function in Binary Tree to set
// the new root
// to have null value for value, and itemTwo for weight
System.out.println(t.getRoot().getValue() + " weight of tree \n");
t1 = t;
TREES.remove(t);
}
for (BinaryTree<Character, Integer> t : TREES){
t2 = t;
System.out.println(t);
}
int weight = t1.getRoot().getElement() + t2.getRoot().getElement();
tFinal.merge(null, weight, t1, t2);
}
Java prevents you from modifying collections in a loop. You will need to use an iterator.
If you want to modify the list while iterating over it, you need to use Iterator.
Below are some SO questions answering this:
Iterating through a Collection, avoiding ConcurrentModificationException when removing in loop
How to modify a Collection while iterating using for-each loop without ConcurrentModificationException?
Your code doesn't compile, so we're limited in the way we can help you. But in general, the way you resolve this issue is to use an Iterator instead of a foreach loop.
For example, this gives a concurrent modification exception:
List<String> l = new ArrayList<String>(asList("a", "b", "c"));
for (String s : l) {
l.remove(s);
}
But this doesn't, and it gives you the result you'd want:
List<String> l = new ArrayList<String>(asList("a", "b", "c"));
for (Iterator<String> iterator = l.iterator(); iterator.hasNext(); ) {
String s = iterator.next();
iterator.remove();
}
System.out.println(l.size());
The latter will output "0".
A solution is to store in another list the items you want to remove, and then, after iterating, remove them.