Home > database >  Hashtable Duplicate values in Serialization
Hashtable Duplicate values in Serialization

Time:11-29

I have an abstract Database class which implements Serializable and has 2 methods for read/write to a .ser file. An example of a Database child would be CredentialsUsers which extends the Database and has a Hashmap<Credentials,User>. When I open my application I load the data with read and after i finish I save the data with load. One of the types of Users supported in my Application is an Administrator,with special privileges. Before I run my Application I make sure to initialize an Admin with initAdmin() below. An admin can check stats like number of users etc. If I rm *.ser my files and run the Application everything is sailing smoothly,but each time I run it I add one more duplicate admin in the Hashmap. I've been at it for 2 hours,added special checks which make no sense(adding a key,pair in the map only if not present etc) and I still can't find why the Databases allow duplicate values. Any ideas? Heres some code about reading/writing(Methods of Abstract Database):

public Object read() {
        Object obj = null;
        try {
            File temp = new File(this.filename);
            temp.createNewFile(); // create file if not present
            FileInputStream fileIn = new FileInputStream(this.filename);
            ObjectInputStream objectIn = new ObjectInputStream(fileIn);
            obj = objectIn.readObject();
            objectIn.close();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return obj;
    }
public void write() {
        try {
            File temp = new File(this.filename);
            temp.createNewFile(); // create file if not present
            FileOutputStream fileOut = new FileOutputStream(this.filename);
            ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);
            objectOut.writeObject(this);
            objectOut.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

And heres the Admin init:

private void initAdmin() throws NoSuchAlgorithmException {
        Admin admin = new Admin(new Credentials("Edward", "password"),
                "Edward", 25, "[email protected]", Gender.MALE, "fakephone");
        if(credentialsUserDatabase.selectUser(admin.getCredentials())==null)//entry not found
            credentialsUserDatabase.insertUser(admin.getCredentials(), admin);
    }

And lastly the implementation:

public class CredentialsUser extends Database {
    @Serial
    private static final long serialVersionUID = 0;
    private HashMap<String, User> users; //Mapping of hash(Credentials) -> User

    public void insertUser(Credentials credentials, User user) throws NoSuchAlgorithmException {
        if (user == null || credentials == null)
            return;
        String hash = Encryption.SHA_512(credentials.toString()); //get credentials hash in a String format
        if (!users.containsKey(hash))
            users.put(hash, user);
    }

You can see the useless checks about duplicate keys everywhere,also maybe the way Im reading a database from the file has to do with it. Here's the last part which may be the problem:

private void loadData(){
    Object temp = credentialsUserDatabase.read();
        if (temp != null) //EOF returns null
            credentialsUserDatabase = (CredentialsUser) temp;
}

Order of methods is loadData()->initAdmin()->Application Runs->write()

CodePudding user response:

You are expecting that result of toString method for two instances of Credentials with the same parameters will be the same, but it is not.

String hash = Encryption.SHA_512(credentials.toString()); //get credentials hash in a String format
        if (!users.containsKey(hash))
            users.put(hash, user);

credentials.toString() will contain a hash of the newly created instance of new Credentials("Edward", "password")

By default, the hashCode method does return distinct integers for distinct objects. This is typically implemented by converting the internal address of the object into an integer (see doc here)

By default, the toString method returns the name of the object’s class plus its hash code (see doc here).

getClass().getName()   '@'   Integer.toHexString(hashCode())

Thereby, when you calculate hash, it will be different for each new instance.

You can ensure in it by creating several new Credentials instances with the same parameter values.

Credentials instance1 = new Credentials("Edward", "password");
Credentials instance2 = new Credentials("Edward", "password");
Console.out.println(instance1.toString());
Console.out.println(instance2.toString());

To overcome this issue, you need to override both equals and hash methods in the Credentials class, so two instances with the same parameters will return the same hash. In this case, the keys for them will be equal, and you will not get duplicates.

You can read about how to do it and why you need to do it in the related answers:

  • Related