2010-04-30 175 views
16

我打算序列化和反序列化键是字符串的hashmap。将键映射为字符串的序列化和反序列化映射

从Josh Bloch的Effective Java中,我了解以下内容。 P.222

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

我的问题是: 1)一般情况下,会忽略equals和重点班的地图的哈希码解决这个问题,图上可以正确恢复? 2)如果我的键是一个字符串,并且String类已经覆盖了hashCode()方法,那么我是否仍然有上述问题。 (我看到一个错误,这使得我认为这可能仍然是一个问题,即使密钥是带重写hashCode的字符串。)

3)以前,我通过序列化一个条目数组(键,值),当反序列化时,我会重建地图。我想知道是否有更好的方法。

4)如果问题1和2的答案仍然不能保证,有人可以解释为什么吗?如果hashCodes是相同的,它们会跨越JVM到同一个存储桶吗?

感谢,格雷斯

+2

如果您试图序列化映射,则需要确保整个对象树是可序列化的,包括键,值和映射实现。 – cs80 2010-04-30 21:51:46

+0

p。 299在第二版 – tiktak 2015-01-11 15:39:39

回答

6

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

1

当使用正确实施的散列表(如java.util.HashMap)时,您不必担心密钥的hashCode()方法。原始文章中第3项中提到的技术实际上是内置在一个很好的散列表实现中的。

默认序列化机制被覆盖。而是存储条目的简单列表(键值为–值)对。在反序列化散列表时,表格的put()方法用于分别重新添加每个条目。这保持了新的反序列化哈希表实例的一致性。密钥的哈希码是否已经改变并不重要;在反序列化时基于密钥的哈希码选择桶。

+0

你对下面的帖子有什么看法,这表明需要做一些额外的工作来正确地序列化hashmap。谢谢。你认为他使用的是不正确实现的散列表吗?谢谢。 http://obscured.info/2007/02/15/serializable-override-hashcode/ – 2010-04-30 22:13:58

+0

是的,他的散列表被打破,而不是他的关键。或者他可能完全误解了这个问题。 – erickson 2010-04-30 22:48:08

20

java.util.HashMap的序列化格式不会序列化存储桶本身,并且哈希代码不是持久状态的一部分。从的Javadoc:

串行数据: HashMap中(桶阵列的长度)的容量被发射 (INT),其次是 HashMap中的大小(的键值 数映射),然后由密钥 (对象)和值(对象),以供 HashMap中的键 - 值映射是顺序发射的 它们是由entrySet().iterator()返回 表示的每个 键值映射关系。

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

的持久状态基本上包括键和值和一些内务。反序列化时,哈希映射是完全重建的;钥匙被重新放置并放置在适当的桶中。

因此,添加字符串键应该工作得很好。我想你的错误在别处。

编辑:这里是一个junit 4测试案例,序列化和反序列化的地图,以及微型虚拟机更改hashcodes。测试通过,尽管反序列化后哈希码不同。

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

} 
+0

你对这篇文章的看法是什么,这表明需要做一些额外的工作才能正确地序列化hashmap。谢谢。 (我觉得它与上面引用的Josh Bloch的段落一致)。http://obscured.info/2007/02/15/serializable-override-hashcode/ – 2010-04-30 22:08:30

+0

这篇文章说在实现Serializable时覆盖hashCode/equals,但实际上规则更普遍 - 覆盖如果您的对象将被用作Map中的键。查看我编辑的测试用例,以证明即使在更改的哈希码下,序列化地图也能正常工作。 – mdma 2010-05-01 00:20:16

+0

几件事情。 1)该声明似乎没有影响'Key.xor = 0x7555AAAA',如果我删除它并运行测试它仍然可以工作。 b)当HashMap包含在另一个对象中时(这是我们正在尝试做的),此方法不起作用。 – 2012-05-01 18:58:36

1

如果一切都失败了,你可以序列化你的地图使用JSON或YAML或XML或其他?

+0

谢谢。我已经使用3),我在上面的帖子中提到,它工作得很好。我只想更好地理解我的其他问题。 – 2010-04-30 22:18:32

0

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

希望这些信息很有用。

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

} 
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); 
    } 
} 
相关问题