简体   繁体   中英

Java: Encountering ConcurrentModificationException. There's something wrong with my iterators and/or HashMaps, and I don't know what it is

I am creating a program that takes two .txt files and prints out the words that appear in both texts and the number of times each shared word appears in each text. I declared two file objects that have valid paths. However, when I try to create two Scanner objects that use the two .txt files, I get FileNotFoundException compiler errors for both lines of code that are declaring the new Scanner objects.

FYI, I use scannerObject.hasNext() in a while loop that adds each word from scannerObject.Next() as a new key in a HashMap variable with a value of 1 or, if the word is already a key in the HashMap, increasing the value (number of occurrences) by 1.

I have tried running the following with both file paths and the simple program below runs without error and outputs "It worked! Hehehe":

import java.io.*;
import java.util.*;

public class readingFilesPractice {
    public static void main(String[] args) {
        try{
            File x = new File("C:\\Users\\aravd.000\\Desktop\\Book1.txt");
            Scanner sc = new Scanner(x);
            while(sc.hasNext()){
                System.out.println(sc.next());
            }
            sc.close();
            System.out.println("It worked! Hehehe");
        }
        catch (Exception e){
            System.out.println("Error!");
        }
    }
}

By the way, the .txt files has areas where there are multiple spaces in succession and stuff like "1.".

The code below runs into two FileNotFoundExceptions (without the try and catch blocks) and in Visual Studios, new Scanner(book1) and new Scanner(book2) have a red squiggly line that states "Unhandled exception type FileNotFoundExceptionJava(16777384)" when I hover over it with my mouse. My complete code for reference is below.

import java.io.*;
import java.util.*;

public class program1 {
    public static void main(String[] args) {
        try {
            File book1 = new File("C:\\Users\\aravd.000\\Desktop\\Book1.txt");
            File book2 = new File("C:\\Users\\aravd.000\\Desktop\\Book2.txt");


            // Counting the number of occurences of each word in book1
            Scanner readBook1 = new Scanner(book1);
            HashMap<String, Integer> wordsInBook1 = new HashMap<String, Integer>();
            while (readBook1.hasNext()) {
                String word = readBook1.next();
                if (wordsInBook1.containsKey(word)) {
                    int occurences = wordsInBook1.get(word) + 1;
                    wordsInBook1.put(word, occurences);
                } else {
                    wordsInBook1.put(word, 1);
                }
            }
            readBook1.close();

            // Counting the number of occurences of each word in book2
            Scanner readBook2 = new Scanner(book2);
            HashMap<String, Integer> wordsInBook2 = new HashMap<String, Integer>();
            while (readBook2.hasNext()) {
                String word = readBook2.next();
                if (wordsInBook2.containsKey(word)) {
                    int occurences = wordsInBook2.get(word) + 1;
                    wordsInBook2.put(word, occurences);
                } else {
                    wordsInBook2.put(word, 1);
                }
            }
            readBook2.close();

            // Creating two iterators for each HashMap
            Iterator wordsInB1Iter = wordsInBook1.entrySet().iterator();
            Iterator wordsInB2Iter = wordsInBook2.entrySet().iterator();

            // Running the wordsInB1Iter iterator to find and delete unique keys in
            // wordsInBook1
            while (wordsInB1Iter.hasNext()) {
                Map.Entry pair = (Map.Entry) wordsInB1Iter.next();
                if (!wordsInBook2.containsKey(pair.getKey())) {
                    wordsInBook1.remove(pair.getKey());
                }
            }

            // Running the wordsInB2Iter iterator to find and delete unique keys
            while (wordsInB2Iter.hasNext()) {
                Map.Entry pair = (Map.Entry) wordsInB2Iter.next();
                if (!wordsInBook1.containsKey(pair.getKey())) {
                    wordsInBook2.remove(pair.getKey());
                }
            }
            System.out.println(wordsInBook1);
            System.out.println(wordsInBook2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

If the other parts of the code are broken, I wouldn't know because I haven't debugged that yet. If you find an error elsewhere, let me know if you want. Thank you for your effort and please let me know if there's anything that needs further clarification!

UPDATE: When I changed my catch block to Exception e and used the e.printStackTrace, my code outputted the following:

java.util.ConcurrentModificationException
        at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1493)
        at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1526)
        at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1524)
        at prorgam1.main(program1.java:50)

Link to error descriptions within the "PROBLEMS" tab in VisualStudios

The picture above may provide more details about the issues with my iterators and HashMaps.

The same answer than @Pedro Borges but

  • Please use generics! Your code is full of cast while it should not.
  • Use Iterator.remove() to remove current value instead of using the source collection. This is the reason your are getting a ConcurrentModificationException .
  • If you don't need the Map.Entry , you may use keySet() instead.
  • You are using Java > 8. If this is Java 11, you may also use var .

Your code:

        Iterator<Map.Entry<String, Integer>>> wordsInB1Iter = wordsInBook1.entrySet().iterator();
        Iterator<Map.Entry<String, Integer>>> wordsInB2Iter = wordsInBook2.entrySet().iterator();

        // Running the wordsInB1Iter iterator to find and delete unique keys in
        // wordsInBook1
        while (wordsInB1Iter.hasNext()) {
            Map.Entry<String,Integer> pair = wordsInB1Iter.next();
            if (!wordsInBook2.containsKey(pair.getKey())) {
                wordsInB1Iter.remove();
            }
        }

        // Running the wordsInB2Iter iterator to find and delete unique keys
        while (wordsInB2Iter.hasNext()) {
            Map.Entry<String,Integer> pair = wordsInB2Iter.next();
            if (!wordsInBook1.containsKey(pair.getKey())) {
                wordsInB2Iter.remove();
            }
        }

And while I'm at it, you may also consider refactoring how your read words:

  • By using a method instead of duplicating the code
  • By using try with resource (Java 7++)
  • By using Map.merge (Java 8++)

As in:

void words(File file) {
  try (Scanner scanner = new Scanner(file)) {
    var result = new HashMap<String,Integer>();
    while (scanner.hasNext()) {
      var word = scanner.next();
      result.merge(word, 1, Integer::sum); // or (a, b) -> a + b
    }
    return result;
  }
}

You may (should?) use a MutableInteger (from common-lang3) to avoid unboxing from Integer to int for performance reasons.

The ConcurrentModificationException comes from the fact you are removing elements from a Set while you're iterating it. That happens because under the hood the iterator is backed by the set, it's not a copy of it.

One way to corner it, although not tremendously elegant is to iterate over a copy of the Set.

If you replace

Iterator wordsInB1Iter = wordsInBook1.entrySet().iterator();
Iterator wordsInB2Iter = wordsInBook2.entrySet().iterator();

with

Iterator wordsInB1Iter = new HashSet<>(wordsInBook1.entrySet()).iterator();
Iterator wordsInB2Iter = new HashSet<>(wordsInBook2.entrySet()).iterator();

you will no longer have concurrent modification.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM