使用键作为字符串序列化和反序列化映射

时间:2010-04-30 21:31:21

标签: java serialization

我打算序列化和反序列化其键是字符串的hashmap。

从Josh Bloch的Effective Java,我理解以下内容。 第222页

  

例如,考虑哈希表的情况。身体上的   表示是包含键值的一系列散列桶   条目。条目放入哪个桶是散列的函数   密钥的代码,通常不保证是相同的   从JVM实现到JVM实现。事实上,它甚至都不是   保证在运行时在同一JVM上运行相同   实现。因此,接受默认的序列化表格   哈希表会构成一个严重的错误。序列化和   反序列化哈希表可能会产生一个不变量的对象   严重腐败。

我的问题是: 1)通常,覆盖地图的密钥类的等于和哈希码会解决此问题并且可以正确恢复地图吗?

2)如果我的键是String并且String类已经覆盖了hashCode()方法,我是否还会遇到上述问题。 (我看到一个错误,让我觉得这可能仍然是一个问题,即使键是重写hashCode的String。)

3)之前,我通过序列化一系列条目(键,值)解决了这个问题,并且在反序列化时我会重建地图。我想知道是否有更好的方法。

4)如果问题1和2的答案仍然无法保证,有人可以解释原因吗?如果hashCodes是相同的,他们会跨JVM转到相同的存储桶吗?

谢谢,Grace

7 个答案:

答案 0 :(得分:22)

java.util.HashMap的序列化形式不会自动序列化存储桶,并且哈希码不是持久状态的一部分。来自javadocs:

  

串行数据:       发出HashMap的容量(桶阵列的长度)   (int),后跟大小   HashMap(键值的数量   映射),然后是密钥   每个的(对象)和值(对象)   由键表示的键值映射   HashMap键值映射是   按照它们的顺序发射   由entrySet().iterator()返回。

来自http://java.sun.com/j2se/1.5.0/docs/api/serialized-form.html#java.util.HashMap

持久状态基本上包括键和值以及一些内务处理。反序列化时,完全重建了hashmap;把钥匙重新放在适当的桶里。

因此,添加String键应该可以正常工作。我猜你的错误在别处。

编辑:这是一个junit 4测试用例,用于序列化和反序列化一个映射,以及minics VMs更改哈希码。尽管反序列化后的哈希码不同,但测试仍然通过。

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);
        }
    }

}

答案 1 :(得分:6)

我99%确定HashMap和HashSet的JVM实现处理了这个问题。他们有自定义序列化和反序列化处理程序。我现在面前没有Bloch的书,但我相信他正在解释这个挑战,而不是说你不能在实践中可靠地序列化java.util.HashMap。

答案 2 :(得分:6)

序列化hashmap:

我试过这个并在我的应用程序中使用它工作正常。 根据您的需要制作此代码的功能。

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();
    }

}

答案 3 :(得分:1)

使用正确实现的哈希表(如java.util.HashMap)时,您不必担心密钥的hashCode()方法。原帖的第3项中提到的技术实际上构建在一个良好的哈希表实现中。

重写默认序列化机制。而是存储简单的条目(键值)对列表。在反序列化哈希表时,表的put()方法用于单独重新添加每个条目。这样可以保持新的反序列化哈希表实例的一致性。密钥的哈希码是否已经改变无关紧要;根据反序列化时密钥的哈希码选择存储桶。

答案 4 :(得分:1)

如果所有其他方法都失败了,您可以使用JSON或YAML或XML等来序列化您的地图吗?

答案 5 :(得分:0)

如果您重新阅读该段落,您会注意到“因此接受哈希表的默认序列化表单会构成一个严重的错误”,这并不意味着Java中的Hash实现使用默认值序列化形式,我相信Java使用自定义序列化来实现其Hash实现。

希望这些信息有用。

答案 6 :(得分:0)

将这些方法添加到包含地图的类中。您还必须添加任何其他字段的序列化/反序列化:

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);
    }
}