web-dev-qa-db-fra.com

Sérialisation et désérialisation d'une carte avec la clé sous forme de chaîne

Je compte sérialiser et désérialiser un hashmap dont la clé est une chaîne.

D'après Java efficace de Josh Bloch, je comprends ce qui suit ... P.222

Par exemple, considérons le cas d'une table de hachage. Le physique La représentation est une séquence de compartiments de hachage contenant la valeur-clé les entrées. Le compartiment dans lequel une entrée est placée est fonction du hachage code de la clé qui, en général, n’est pas garanti identique de l'implémentation JVM à l'implémentation JVM. En fait, ce n'est même pas garantie d'être identique depuis l'exécution sur la même machine virtuelle la mise en oeuvre. Donc accepter le formulaire sérialisé par défaut pour un table de hachage constituerait un sérieux bug. Sérialisation et La désérialisation de la table de hachage pourrait générer un objet dont les invariants étaient gravement corrompus.

Mes questions sont les suivantes: 1) En général, la substitution d'égaux et de hashcode de la classe de clés de la carte résoudrait ce problème et la carte pourrait être restaurée correctement?

2) Si ma clé est une chaîne et que la classe String redéfinit déjà la méthode hashCode (), aurais-je toujours le problème décrit ci-dessus . la clé est String avec hashCode.

3) Auparavant, je contournais ce problème en sérialisant un tableau d'entrées (clé, valeur) et, lors de la désérialisation, je reconstruisais la carte. Je me demande s'il existe une meilleure approche.

4) Si les réponses aux questions 1 et 2 sont qu'il ne peut toujours pas être garanti, quelqu'un pourrait-il expliquer pourquoi? Si les codes de hachage sont identiques, iraient-ils dans les mêmes compartiments sur les machines virtuelles?

Merci Grace

17
Grace K

La forme de sérialisation de Java.util.HashMap ne sérialise pas les compartiments eux-mêmes et le code de hachage ne fait pas partie de l'état persistant. Des javadocs:

Données série: La capacité de HashMap (la longueur du groupe de baies) est émise (int), suivi de la taille du fichier HashMap (le nombre de correspondances clé-valeur ), Suivi de la clé (Objet) et valeur (Objet) pour chaque mappage clé-valeur représenté par le HashMap Les mappages clé-valeur sont émis dans l’ordre où ils sont retourné par entrySet().iterator().

de http://Java.Sun.com/j2se/1.5.0/docs/api/serialized-form.html#Java.util.HashMap

L'état persistant comprend essentiellement les clés et les valeurs, ainsi que des tâches ménagères. Une fois désérialisé, le hashmap est complètement reconstruit. les clés sont remises et placées dans des seaux appropriés.

Donc, ajouter des clés de chaîne devrait bien fonctionner. Je suppose que votre bug est ailleurs.

EDIT: Voici un scénario de test pour Junit 4 qui sérialise et désérialise une carte, ainsi que des machines virtuelles miniques modifiant les codes de hachage. Le test réussit bien que les codes de hachage soient différents après la désérialisation. 

import org.junit.Assert;
import org.junit.Test;

import Java.io.*;
import Java.util.HashMap;

public class HashMapTest
{
    @Test
    public void testHashMapSerialization() throws IOException, ClassNotFoundException
    {
        HashMap map = new HashMap();
        map.put(new Key("abc"), 1);
        map.put(new Key("def"), 2);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(map);
        objOut.close();
        Key.xor = 0x7555AAAA; // make the hashcodes different
        ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
        HashMap actual = (HashMap) objIn.readObject();
        // now try to get a value
        Assert.assertEquals(2, actual.get(new Key("def")));
    }

    static class Key implements Serializable
    {
        private String  keyString;
        static int xor = 0;

        Key(String keyString)
        {
            this.keyString = keyString;
        }

        @Override
        public int hashCode()
        {
            return keyString.hashCode()^xor;
        }

        @Override
        public boolean equals(Object obj)
        {
            Key otherKey = (Key) obj;
            return keyString.equals(otherKey.keyString);
        }
    }

}
21
mdma

Pour sérialiser un hashmap:

J'ai essayé cela et utilisé dans mon application, il fonctionne très bien ..__ Faites une fonction de ce code en fonction de vos besoins.

public static void main(String arr[])
{
    Map<String,String> hashmap=new HashMap<String,String>();
    hashmap.put("key1","value1");
    hashmap.put("key2","value2");
    hashmap.put("key3","value3");
    hashmap.put("key4","value4");

    FileOutputStream fos;
    try {
        fos = new FileOutputStream("c://list.ser");

        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(hashmap);
        oos.close();

        FileInputStream fis = new FileInputStream("c://list.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Map<String,String> anotherList = (Map<String,String>) ois.readObject();

        ois.close();

        System.out.println(anotherList);

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

}
6
Kumar Gaurav

Je suis sûr à 99% que l'implémentation JVM de HashMap et HashSet résout ce problème. Ils ont un gestionnaire de sérialisation et de désérialisation personnalisé. Je n'ai pas le livre de Bloch sous les yeux, mais je crois qu'il explique le défi, sans dire qu'il est impossible de sérialiser de manière fiable un fichier Java.util.HashMap.

6
Yishai

Lorsque vous utilisez une table de hachage correctement implémentée (telle que Java.util.HashMap), vous n'avez pas à vous soucier de la méthode hashCode() de vos clés. La technique mentionnée à l'article 3 du message d'origine est en réalité intégrée à une bonne implémentation de table de hachage.

Le mécanisme de sérialisation par défaut est remplacé. Une simple liste d'entrées paires (clé – valeur) est stockée à la place. Lors de la désérialisation de la table de hachage, la méthode put() de la table est utilisée pour rajouter chaque entrée individuellement. Ceci maintient la cohérence de la nouvelle instance de table de hachage désérialisée. Peu importe si les codes de hachage des clés ont changé. le compartiment est choisi en fonction du code de hachage de la clé au moment de la désérialisation.

1
erickson

Si tout le reste échoue, pouvez-vous sérialiser votre carte en utilisant JSON ou YAML ou XML ou autre

1
skiphoppy

Ajoutez ces méthodes à la classe contenant la carte. Vous devez également ajouter la sérialisation/dé-sérialisation de tout autre champ:

private void writeObject(ObjectOutputStream stream) throws IOException {
    stream.writeInt(map.size());
    for (Entry<String, String> entry : map.entrySet()) {
        stream.writeObject(entry.getKey());
        stream.writeObject(entry.getValue());
    }
}


private void readObject(ObjectInputStream stream) throws IOException,
        ClassNotFoundException {
    int mapSize = stream.readInt();
    for (int i = 0; i < mapSize; i++) {
        String key = (String) stream.readObject();
        String value = (String) stream.readObject();
        map.put(key, value);
    }
}
0
Riadh

Si vous relisez le paragraphe, vous remarquerez que " Par conséquent, accepter le formulaire sérialisé par défaut pour une table de hachage constituerait un problème sérieux", cela ne signifie pas que les implémentations de hachage en Java utilisent un formulaire sérialisé par défaut, je utilise la sérialisation personnalisée pour ses implémentations de hachage.

J'espère que cette information est utile.

0
dhananjayan