如何深度复制具有初始容量的地图?

时间:2016-08-28 16:37:13

标签: java serialization hashmap deserialization linkedhashmap

序列化和反序列化是深度复制具有复杂图形(How do you make a deep copy of an object in Java?等)的对象的首选和可接受的方法,其中复制构造函数/工厂方法方法不太适合。

但是,此方法不适用于指定初始容量的地图。我之前的问题(Why does specifying Map's initial capacity cause subsequent serializations to give different results?)表明结果对象不相等,答案显示它们的字节表示不同:如果序列化给出byte[] b1,那么再次反序列化和序列化将给出{{ 1}}与byte[] b2不同(至少1个元素不同)。这与de / serializing对象的通常行为形成对比。

控制de /序列化过程的b1readObject方法是writeObject,因此无法覆盖 - 这可能是故意(Hashmap slower after deserialization - Why?)。< / p>

我在包含许多其他对象(包括地图)的对象上使用de / serialization深层复制方法。我也在对它们的字节数组表示进行比较和更改。只要没有使用初始容量参数初始化地图,一切都运行良好。但是,如上所述,尝试通过指定初始容量来优化地图会破坏这种方法。

我想知道是否有可能绕过这个问题,如果是这样的话。

1 个答案:

答案 0 :(得分:1)

好的,首先,你通过关注指定初始容量导致不同的序列化字节这一事实,咆哮错误的树。事实上,如果你看一下差异:

pbA from your example:
: ac ed 00 05 73 72 00 0f 71 33 39 31 39 33 34 39   ....sr..q3919349
: 34 2e 53 74 61 74 65 00 00 00 00 00 00 00 01 02   4.State.........
: 00 01 4c 00 04 6d 61 70 73 74 00 10 4c 6a 61 76   ..L..mapst..Ljav
: 61 2f 75 74 69 6c 2f 4c 69 73 74 3b 78 70 73 72   a/util/List;xpsr
: 00 13 6a 61 76 61 2e 75 74 69 6c 2e 41 72 72 61   ..java.util.Arra
: 79 4c 69 73 74 78 81 d2 1d 99 c7 61 9d 03 00 01   yListx.....a....
: 49 00 04 73 69 7a 65 78 70 00 00 00 01 77 04 00   I..sizexp....w..
: 00 00 01 73 72 00 14 71 33 39 31 39 33 34 39 34   ...sr..q39193494
: 2e 4d 61 70 57 72 61 70 70 65 72 00 00 00 00 00   .MapWrapper.....
: 00 00 01 02 00 01 4c 00 03 6d 61 70 74 00 0f 4c   ......L..mapt..L
: 6a 61 76 61 2f 75 74 69 6c 2f 4d 61 70 3b 78 70   java/util/Map;xp
: 73 72 00 11 6a 61 76 61 2e 75 74 69 6c 2e 48 61   sr..java.util.Ha
: 73 68 4d 61 70 05 07 da c1 c3 16 60 d1 03 00 02   shMap......`....
: 46 00 0a 6c 6f 61 64 46 61 63 74 6f 72 49 00 09   F..loadFactorI..
: 74 68 72 65 73 68 6f 6c 64 78 70 3f 40 00 00 00   thresholdxp?@...
: 00 00 02 77 08 00 00 00 02 00 00 00 00 78 78      ...w.........xx 

zero from your example:
: ac ed 00 05 73 72 00 0f 71 33 39 31 39 33 34 39   ....sr..q3919349
: 34 2e 53 74 61 74 65 00 00 00 00 00 00 00 01 02   4.State.........
: 00 01 4c 00 04 6d 61 70 73 74 00 10 4c 6a 61 76   ..L..mapst..Ljav
: 61 2f 75 74 69 6c 2f 4c 69 73 74 3b 78 70 73 72   a/util/List;xpsr
: 00 13 6a 61 76 61 2e 75 74 69 6c 2e 41 72 72 61   ..java.util.Arra
: 79 4c 69 73 74 78 81 d2 1d 99 c7 61 9d 03 00 01   yListx.....a....
: 49 00 04 73 69 7a 65 78 70 00 00 00 01 77 04 00   I..sizexp....w..
: 00 00 01 73 72 00 14 71 33 39 31 39 33 34 39 34   ...sr..q39193494
: 2e 4d 61 70 57 72 61 70 70 65 72 00 00 00 00 00   .MapWrapper.....
: 00 00 01 02 00 01 4c 00 03 6d 61 70 74 00 0f 4c   ......L..mapt..L
: 6a 61 76 61 2f 75 74 69 6c 2f 4d 61 70 3b 78 70   java/util/Map;xp
: 73 72 00 11 6a 61 76 61 2e 75 74 69 6c 2e 48 61   sr..java.util.Ha
: 73 68 4d 61 70 05 07 da c1 c3 16 60 d1 03 00 02   shMap......`....
: 46 00 0a 6c 6f 61 64 46 61 63 74 6f 72 49 00 09   F..loadFactorI..
: 74 68 72 65 73 68 6f 6c 64 78 70 3f 40 00 00 00   thresholdxp?@...
: 00 00 00 77 08 00 00 00 01 00 00 00 00 78 78      ...w.........xx 

唯一的区别是the couple of bytes that specify load factor and such。显然,如果您指定了第一次反序列化忽略的不同初始容量,那么这些字节将是不同的 - course 这是一只红鲱鱼。

您担心损坏的深层副本,但这种担忧是错误的。 唯一重要的是,在正确性方面,是反序列化的结果。它只需要是一个正确的,功能齐全的深层副本,不会违反任何程序&#39 ; s不变量。专注于精确的序列化字节是一种分心:你不关心它们,事实上你只关心结果是否正确。

这将我们带到下一点:

这里面临的唯一真正的问题是长期性能(速度和内存)特性的差异,因为某些Java版本在反序列化时会忽略初始映射容量。 这不会影响您的数据(也就是说,它不会破坏不变量),它只会影响性能。

所以第一步 确保这实际上是一个问题 。也就是说,它可归结为潜在的过早优化问题:忽略反序列化地图现在的初始容量的差异。如果您的应用程序运行时具有足够的性能特征,那么您无需担心。如果它没有,并且如果您能够将瓶颈缩小到由于不同的初始容量而降低的反序列化哈希映射性能,那么只有然后才能接近这个问题。

所以,这个答案的最后一部分是, if 你确定反序列化地图的性能特征实际上是不够的,你可以做很多事情。

我能想到的最简单,最明显的一点就是在你的对象上实现readResolve(),并借此机会:

  1. 使用适当的参数(初始容量等)构建新地图
  2. 将旧的反序列化地图中的所有项目复制到新的项目。
  3. 然后丢弃旧地图并将其替换为新地图。
  4. 示例(从原始代码示例中,选择产生&#34; false&#34;结果的地图):

    class MapWrapper implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        Map<String, Integer> map = new HashMap<>(2);
    
        private Object readResolve () throws ObjectStreamException {
            // Replace deserialized 'map' with one that has the desired
            // capacity parameters.
            Map<String, Integer> fixedMap = new HashMap<>(2);
            fixedMap.putAll(map);
            map = fixedMap;
            return this;
        }
    
    }
    

    但首先要问的是,这是否真的给你造成了问题。我相信你是在思考它,而且过分关注逐字节数字序列化数据比较并不适合你。