Java 使用键作为字符串序列化和反序列化映射
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2747819/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Serializing and deserializing a map with key as string
提问by Grace K
I am intending to serialize and deserialize a hashmap whose key is a string.
我打算序列化和反序列化一个键是字符串的哈希图。
From Josh Bloch's Effective Java, I understand the following. P.222
从 Josh Bloch 的 Effective Java 中,我了解以下内容。P.222
For example, consider the case of a hash table. The physical representation is a sequence of hash buckets containing key-value entries. Which bucket an entry is placed in is a function of the hash code of the key, which is not, in general guaranteed to be the same from JVM implementation to JVM implementation. In fact, it isn't even guaranteed to be the same from run to run on the same JVM implementation. Therefore accepting the default serialized form for a hash table would constitute a serious bug. Serializing and deserializing the hash table could yield an object whose invariants were seriously corrupt.
例如,考虑哈希表的情况。物理表示是包含键值条目的哈希桶序列。条目放在哪个桶中是键的哈希码的函数,通常不能保证从 JVM 实现到 JVM 实现是相同的。事实上,甚至不能保证在同一个 JVM 实现上从运行到运行都是相同的。因此,接受散列表的默认序列化形式将构成严重的错误。对哈希表进行序列化和反序列化可能会产生一个不变量严重损坏的对象。
My questions are: 1) In general, would overriding equals and hashcode of the key class of the map resolve this issue and the map can be correctly restored?
我的问题是: 1)一般来说,覆盖映射的关键类的等号和哈希码会解决这个问题并且可以正确恢复映射吗?
2) If my key is a String and the String class is already overriding the hashCode() method, would I still have problem described above. (I am seeing a bug which makes me think this is probably still a problem even though the key is String with overriding hashCode.)
2)如果我的键是一个字符串并且字符串类已经覆盖了 hashCode() 方法,我是否还会遇到上述问题。(我看到一个错误,这让我认为这可能仍然是一个问题,即使键是具有覆盖 hashCode 的字符串。)
3)Previously, I got around this issue by serializing an array of entries (key, value) and when deserializing I would reconstruct the map. I am wondering if there is a better approach.
3)以前,我通过序列化一组条目(键、值)来解决这个问题,反序列化时我会重建映射。我想知道是否有更好的方法。
4) If the answers to question 1 and 2 are that it still can't be guaranteed, could someone explain why? If the hashCodes are the same would they go to the same buckets across JVMs?
4)如果问题1和2的答案仍然不能保证,有人可以解释为什么吗?如果 hashCode 相同,它们会跨 JVM 进入相同的存储桶吗?
Thanks, Grace
谢谢,格蕾丝
回答by Yishai
I'm 99% sure that the JVM implementation of HashMap and HashSet handle this issue. They have a custom serialization and deserialization handler. I don't have Bloch's book in front of me now, but I believe he is explaining the challange, not saying that you can't reliably serialize a java.util.HashMap in practice.
我 99% 确定 HashMap 和 HashSet 的 JVM 实现处理这个问题。他们有一个自定义的序列化和反序列化处理程序。我现在没有 Bloch 的书在我面前,但我相信他正在解释挑战,并不是说你不能在实践中可靠地序列化 java.util.HashMap。
回答by erickson
When using a correctly implemented hash table (like java.util.HashMap
), you don't have to worry about the hashCode()
method of your keys. The technique referred to in item #3 of the original post is actually built into a good hash table implementation.
当使用正确实现的哈希表(如java.util.HashMap
)时,您不必担心hashCode()
键的方法。原始帖子第 3 项中提到的技术实际上内置于一个好的哈希表实现中。
The default serialization mechanism is overridden. A simple list of entries (key–value) pairs is stored instead. When deserializing the hash table, the table's put()
method is used to re-add each entry individually. This maintains the consistency of the new, deserialized hash table instance. It won't matter if the hash codes of the keys have changed; the bucket is chosen based on the hash code of the key at the time of deserialization.
默认的序列化机制被覆盖。而是存储一个简单的条目(键值)对列表。反序列化哈希表时,表的put()
方法用于单独重新添加每个条目。这保持了新的、反序列化的哈希表实例的一致性。键的哈希码是否发生变化并不重要;桶是根据反序列化时键的哈希码选择的。
回答by mdma
The serialization form of java.util.HashMap
doesn't serialize the buckets themselves, and the hash code is not part of the persisted state. From the javadocs:
的序列化形式java.util.HashMap
不序列化桶本身,哈希码不是持久化状态的一部分。从javadocs:
Serial Data: The capacity of the HashMap (the length of the bucket array) is emitted (int), followed by the size of the HashMap (the number of key-value mappings), followed by the key (Object) and value (Object) for each key-value mapping represented by the HashMap The key-value mappings are emitted in the order that they are returned by
entrySet().iterator()
.
Serial Data:发出HashMap的容量(bucket数组的长度)(int),后面是HashMap的大小(key-value映射的个数),后面是key(Object)和value(Object) ) 对于由 HashMap 表示的每个键值映射 键值映射按它们返回的顺序发出
entrySet().iterator()
。
from http://java.sun.com/j2se/1.5.0/docs/api/serialized-form.html#java.util.HashMap
来自http://java.sun.com/j2se/1.5.0/docs/api/serialized-form.html#java.util.HashMap
The persisted state basically comprises the keys and values and some housekeeping. When deserialized, the hashmap is completely rebuilt; the keys are rehashed and placed in appropriate buckets.
持久状态基本上包括键和值以及一些内务处理。反序列化时,hashmap完全重建;密钥被重新散列并放置在适当的存储桶中。
So, adding String keys should work just fine. I would guess your bug lies elsewhere.
因此,添加字符串键应该可以正常工作。我猜你的错误在别处。
EDIT: Here's a junit 4 test case that serializes and deserializes a map, and minics VMs changing hashcodes. The test passes, despite the hashcodes being different after deserialization.
编辑:这是一个 junit 4 测试用例,它序列化和反序列化地图,以及更改哈希码的微型虚拟机。尽管反序列化后哈希码不同,但测试通过。
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);
}
}
}
回答by skiphoppy
If all else fails, can you serialize your Map using JSON or YAML or XML or something?
如果所有其他方法都失败了,您可以使用 JSON 或 YAML 或 XML 或其他方式序列化您的 Map 吗?
回答by dhananjayan
If you re-read the paragraph you would notice "Therefore accepting the default serialized form for a hash tablewould constitute a serious bug", It doesn't mean the Hash implementations in Java use default serialized form, I believe Java uses custom serialization for its Hash implementations.
如果您重新阅读该段落,您会注意到“因此接受哈希表的默认序列化形式将构成严重错误”,这并不意味着 Java 中的 Hash 实现使用默认序列化形式,我相信 Java 使用自定义序列化它的哈希实现。
Hope this information is useful.
希望这些信息有用。
回答by Kumar Gaurav
To serialize a hashmap:
要序列化哈希图:
I have tried this and used in my app it is working fine. Make a function of this code according to your need.
我已经尝试过这个并在我的应用程序中使用它工作正常。根据您的需要制作此代码的功能。
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();
}
}
回答by Riadh
Add these methods to the class that contains the map. You have also to add serialisation/de-serialization of any other fields:
将这些方法添加到包含地图的类。您还必须添加任何其他字段的序列化/反序列化:
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);
}
}