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

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

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

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

我的问题是:1)一般来说,覆盖地图关键类的等于和哈希码会解决这个问题,地图可以正确恢复吗?

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

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

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

谢谢,格雷斯

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

串行数据:HashMap的容量(桶数组的长度)发出(int),后跟HashMap的大小(键值映射的数量),后跟键(Object)和值(Object) )对于由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); } } } 

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

要序列化hashmap:

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

 public static void main(String arr[]) { Map hashmap=new HashMap(); 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 anotherList = (Map) ois.readObject(); ois.close(); System.out.println(anotherList); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } 

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

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

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

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

希望这些信息有用。

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

 private void writeObject(ObjectOutputStream stream) throws IOException { stream.writeInt(map.size()); for (Entry 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); } }