本文介绍了HashMap的奇怪序列化行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下三个类:


  • EntityTransformer 包含将 Entity关联的地图(带有字符串)

  • 实体是一个包含ID(由equals / hashcode使用)的对象,并且包含对 EntityTransformer (请注意循环依赖项)

  • SomeWrapper 包含一个 EntityTransformer ,并维护一个与 Entity关联的地图的标识符和相应的 Entity 对象。

  • EntityTransformer contains a map associating an Entity with a String
  • Entity is an object containing an ID (used by equals / hashcode), and which contains a reference to an EntityTransformer (note the circular dependency)
  • SomeWrapper contains an EntityTransformer, and maintains a Map associating Entity's identifiers and the corresponding Entity object.

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

The following code will create an EntityTransformer and a Wrapper, add two entities to the Wrapper, serialize it, deserialize it and test the presence of the two entitites:

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

输出为:

false

true

所以基本上,序列化以某种方式失败了,因为映射应该包含两个实体作为Key。我怀疑Entity和EntityTransformer之间的循环依赖关系,实际上,如果我将Entity的EntityManager实例变量设为静态,它就可以工作。

So basically, the serialization failed somehow, as the map should contain both entities as Keys. I suspect the cyclic dependency between Entity and EntityTransformer, and indeed if I make static the EntityManager instance variable of Entity, it works.

问题1 :考虑到我对这种周期性依赖的坚持,我该如何克服这个问题?

Question 1: given that I'm stuck with this cyclic dependency, how could I overcome this issue ?

另一个很奇怪的事情:如果我删除了在包装器中维护标识符和实体之间关联的Map,那么一切正常...

Another very weird thing: if I remove the Map maintaining an association between identifiers and Entities in the Wrapper, everything works fine... ??

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

Question 2: someone understand what's going on here ?

如果您想测试以下内容,Balllow是完整功能的代码:

Bellow is a full functional code if you want to test it:

在此先感谢您的帮助:)

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

编辑

此问题的潜在影响有一个很好的摘要:

There is a good summary of what is potentially in play for this issue:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4957674

如果某个键包含(直接或间接)对
映射的循环引用,则在$期间可能执行以下顺序b $ b反序列化---如果密钥在
哈希表之前写入对象流:

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. 实例化密钥

  2. 反序列化键的属性
    2a。反序列化HashMap(由密钥直接或间接指向)
    2a-1。实例化HashMap
    2a-2。读取键和值
    2a-3。在键上调用hashCode()以重新哈希地图
    2b。反序列化键的剩余属性

由于2a-3在2b之前执行,hashCode()可能返回错误的
答案,因为键的属性尚未完全反序列化

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

现在,这不能完全解释为什么可以解决此问题如果从包装器中删除了HashMap,或移到EntityTransformer类。

Now that does not explain fully why the issue can be fixed if the HashMap from Wrapper is removed, or move to the EntityTransformer class.

推荐答案

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

This is a problem with circular initialisation. Whilst Java Serialisation can handle arbitrary cycles, the initialisation has to happen in some order.

AWT中也存在类似的问题,其中 Component Entity )包含对其父级 Container EntityTransformer )。 AWT的作用是在 Component transient 中创建父引用。

There's a similar problem in AWT where Component (Entity) contains a reference to its parent Container (EntityTransformer). What AWT does is to make the parent reference in Component transient.

transient Container parent;

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

So now each Component can complete its initialisation before Container.readObject adds it back in:

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

这篇关于HashMap的奇怪序列化行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-23 19:36