gpt4 book ai didi

java - HashMap 的奇怪序列化行为

转载 作者:搜寻专家 更新时间:2023-10-31 19:48:32 25 4
gpt4 key购买 nike

考虑以下三个类:

  • EntityTransformer 包含将 Entity 与字符串相关联的映射
  • Entity 是一个包含 ID(由 equals/hashcode 使用)的对象,并且包含对 EntityTransformer 的引用(注意循环依赖)
  • SomeWrapper 包含一个EntityTransformer,并维护一个 Map 关联Entity 的标识符和相应的Entity 对象.

以下代码将创建一个 EntityTransformer 和一个 Wrapper,将两个实体添加到 Wrapper,对其进行序列化、反序列化并测试两个实体的存在:

public static void main(String[] args)
throws Exception {

EntityTransformer et = new EntityTransformer();
Wrapper wr = new Wrapper(et);

Entity a1 = wr.addEntity("a1"); // a1 and a2 are created internally by the Wrapper
Entity a2 = wr.addEntity("a2");

byte[] bs = object2Bytes(wr);
wr = (SomeWrapper) bytes2Object(bs);

System.out.println(wr.et.map);
System.out.println(wr.et.map.containsKey(a1));
System.out.println(wr.et.map.containsKey(a2));
}

输出是:

{a1=whatever-a1, a2=whatever-a2}

false

true

所以基本上,序列化以某种方式失败了,因为 map 应该包含两个实体作为键。我怀疑 Entity 和 EntityTransformer 之间存在循环依赖关系,事实上,如果我将 Entity 的 EntityManager 实例变量设置为静态,它就会起作用。

问题 1:鉴于我陷入了这种循环依赖,我该如何克服这个问题?

另一个非常奇怪的事情:如果我删除维护包装器中标识符和实体之间关联的 map ,一切正常...... ??

问题 2:有人明白这里发生了什么吗?

下面是一个完整的功能代码,如果你想测试它的话:

提前感谢您的帮助:)

public class SerializeTest {

public static class Entity
implements Serializable
{
private EntityTransformer em;
private String id;

Entity(String id, EntityTransformer em) {
this.id = id;
this.em = em;
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Entity other = (Entity) obj;
if ((this.id == null) ? (other.id != null) : !this.id.equals(
other.id)) {
return false;
}
return true;
}

@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + (this.id != null ? this.id.hashCode() : 0);
return hash;
}

public String toString() {
return id;
}
}

public static class EntityTransformer
implements Serializable
{
Map<Entity, String> map = new HashMap<Entity, String>();
}

public static class Wrapper
implements Serializable
{
EntityTransformer et;
Map<String, Entity> eMap;

public Wrapper(EntityTransformer b) {
this.et = b;
this.eMap = new HashMap<String, Entity>();
}

public Entity addEntity(String id) {
Entity e = new Entity(id, et);
et.map.put(e, "whatever-" + id);
eMap.put(id, e);

return e;
}
}

public static void main(String[] args)
throws Exception {
EntityTransformer et = new EntityTransformer();
Wrapper wr = new Wrapper(et);

Entity a1 = wr.addEntity("a1"); // a1 and a2 are created internally by the Wrapper
Entity a2 = wr.addEntity("a2");

byte[] bs = object2Bytes(wr);
wr = (Wrapper) bytes2Object(bs);

System.out.println(wr.et.map);
System.out.println(wr.et.map.containsKey(a1));
System.out.println(wr.et.map.containsKey(a2));
}



public static Object bytes2Object(byte[] bytes)
throws IOException, ClassNotFoundException {
ObjectInputStream oi = null;
Object o = null;
try {
oi = new ObjectInputStream(new ByteArrayInputStream(bytes));
o = oi.readObject();
}
catch (IOException io) {
throw io;
}
catch (ClassNotFoundException cne) {
throw cne;
}
finally {
if (oi != null) {
oi.close();
}
}

return o;
}

public static byte[] object2Bytes(Object o)
throws IOException {
ByteArrayOutputStream baos = null;
ObjectOutputStream oo = null;
byte[] bytes = null;
try {
baos = new ByteArrayOutputStream();
oo = new ObjectOutputStream(baos);

oo.writeObject(o);
bytes = baos.toByteArray();
}
catch (IOException ex) {
throw ex;
}
finally {
if (oo != null) {
oo.close();
}
}

return bytes;
}
}

编辑

对于此问题的潜在影响,有一个很好的总结: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4957674

The problem is that HashMap's readObject() implementation , in order to re-hash the map, invokes the hashCode() method of some of its keys, regardless of whether those keys have been fully deserialized.

If a key contains (directly or indirectly) a circular reference to the map, the following order of execution is possible during deserialization --- if the key was written to the object stream before the hashmap:

  1. Instantiate the key
  2. Deserialize the key's attributes 2a. Deserialize the HashMap (which was directly or indirectly pointed to by the key) 2a-1. Instantiate the HashMap 2a-2. Read keys and values 2a-3. Invoke hashCode() on the keys to re-hash the map 2b. Deserialize the key's remaining attributes

Since 2a-3 is executed before 2b, hashCode() may return the wrong answer, because the key's attributes have not yet been fully deserialized.

现在这并不能完全解释为什么如果移除 Wrapper 中的 HashMap 或移动到 EntityTransformer 类,问题可以得到解决。

最佳答案

这是循环初始化的问题。虽然 Java 序列化可以处理任意周期,但初始化必须按某种顺序进行。

AWT 中存在类似的问题,其中 Component (Entity) 包含对其父级 Container (EntityTransformer). AWT 所做的是使 Component 中的父引用 transient

transient Container parent;

现在每个 Component 都可以在 Container.readObject 将其添加回之前完成其初始化:

    for(Component comp : component) {
comp.parent = this;

关于java - HashMap 的奇怪序列化行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10031833/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com