【问题标题】:Serialization round trip of hash map does not preserve order哈希映射的序列化往返不保留顺序
【发布时间】:2014-04-18 23:48:44
【问题描述】:

我注意到,在最近版本的 Java (1.7.0_u51) 中,哈希映射的序列化和反序列化不再保留哈希映射中元素的顺序。请看下面的例子:

@Test
public void test() throws IOException, ClassNotFoundException {
    HashMap<String, String> map1 = new HashMap<>();
    map1.put("a1234567", "aaa");
    map1.put("b1234567", "bbb");

    System.out.println("Map1: " + map1.toString());

    byte[] serializedMap1 = objectToBytes(map1);

    System.out.println("Map1 Serialized: " + Arrays.toString(serializedMap1));

    Object map2 = bytesToObject(serializedMap1);

    System.out.println("Map2: " + map2.toString());

    byte[] serializedMap2 = objectToBytes((Serializable) map2);

    System.out.println("Map2 Serialized: " + Arrays.toString(serializedMap2));

    Object map3 = bytesToObject(serializedMap2);

    System.out.println("Map3: " + map3.toString());

    byte[] serializedMap3 = objectToBytes((Serializable) map3);

    System.out.println("Map3 Serialized: " + Arrays.toString(serializedMap3));

    Object map4 = bytesToObject(serializedMap3);

    System.out.println("Map4: " + map4.toString());

    byte[] serializedMap4 = objectToBytes((Serializable) map4);

    System.out.println("Map4 Serialized: " + Arrays.toString(serializedMap4));
}

private byte[] objectToBytes(Serializable obj) throws IOException {
    PoolByteArrayOutputStream bos = new PoolByteArrayOutputStream();
    try {
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(obj);
        byte[] bytes = bos.toByteArray();
        oos.close();
        return bytes;
    } finally {
        bos.close();
    }
}

private Object bytesToObject(byte[] str) throws IOException, ClassNotFoundException {
    ByteArrayInputStream bis = new ByteArrayInputStream(str);
    ObjectInputStream ois = new ClassLoaderObjectInputStream(bis, null);

    Object obj = ois.readObject();
    ois.close();
    bis.close();
    return obj;
}

上面的测试会输出:

Map1: {a1234567=aaa, b1234567=bbb}
Map1 Serialized: [-84, -19, 0, 5, 115, 114, 0, 17, 106, 97, 118, 97, 46, 117, 116, 105, 108, 46, 72, 97, 115, 104, 77, 97, 112, 5, 7, -38, -63, -61, 22, 96, -47, 3, 0, 2, 70, 0, 10, 108, 111, 97, 100, 70, 97, 99, 116, 111, 114, 73, 0, 9, 116, 104, 114, 101, 115, 104, 111, 108, 100, 120, 112, 63, 64, 0, 0, 0, 0, 0, 12, 119, 8, 0, 0, 0, 16, 0, 0, 0, 2, 116, 0, 8, 97, 49, 50, 51, 52, 53, 54, 55, 116, 0, 3, 97, 97, 97, 116, 0, 8, 98, 49, 50, 51, 52, 53, 54, 55, 116, 0, 3, 98, 98, 98, 120]
Map2: {b1234567=bbb, a1234567=aaa}
Map2 Serialized: [-84, -19, 0, 5, 115, 114, 0, 17, 106, 97, 118, 97, 46, 117, 116, 105, 108, 46, 72, 97, 115, 104, 77, 97, 112, 5, 7, -38, -63, -61, 22, 96, -47, 3, 0, 2, 70, 0, 10, 108, 111, 97, 100, 70, 97, 99, 116, 111, 114, 73, 0, 9, 116, 104, 114, 101, 115, 104, 111, 108, 100, 120, 112, 63, 64, 0, 0, 0, 0, 0, 1, 119, 8, 0, 0, 0, 2, 0, 0, 0, 2, 116, 0, 8, 98, 49, 50, 51, 52, 53, 54, 55, 116, 0, 3, 98, 98, 98, 116, 0, 8, 97, 49, 50, 51, 52, 53, 54, 55, 116, 0, 3, 97, 97, 97, 120]
Map3: {a1234567=aaa, b1234567=bbb}
Map3 Serialized: [-84, -19, 0, 5, 115, 114, 0, 17, 106, 97, 118, 97, 46, 117, 116, 105, 108, 46, 72, 97, 115, 104, 77, 97, 112, 5, 7, -38, -63, -61, 22, 96, -47, 3, 0, 2, 70, 0, 10, 108, 111, 97, 100, 70, 97, 99, 116, 111, 114, 73, 0, 9, 116, 104, 114, 101, 115, 104, 111, 108, 100, 120, 112, 63, 64, 0, 0, 0, 0, 0, 1, 119, 8, 0, 0, 0, 2, 0, 0, 0, 2, 116, 0, 8, 97, 49, 50, 51, 52, 53, 54, 55, 116, 0, 3, 97, 97, 97, 116, 0, 8, 98, 49, 50, 51, 52, 53, 54, 55, 116, 0, 3, 98, 98, 98, 120]
Map4: {b1234567=bbb, a1234567=aaa}
Map4 Serialized: [-84, -19, 0, 5, 115, 114, 0, 17, 106, 97, 118, 97, 46, 117, 116, 105, 108, 46, 72, 97, 115, 104, 77, 97, 112, 5, 7, -38, -63, -61, 22, 96, -47, 3, 0, 2, 70, 0, 10, 108, 111, 97, 100, 70, 97, 99, 116, 111, 114, 73, 0, 9, 116, 104, 114, 101, 115, 104, 111, 108, 100, 120, 112, 63, 64, 0, 0, 0, 0, 0, 1, 119, 8, 0, 0, 0, 2, 0, 0, 0, 2, 116, 0, 8, 98, 49, 50, 51, 52, 53, 54, 55, 116, 0, 3, 98, 98, 98, 116, 0, 8, 97, 49, 50, 51, 52, 53, 54, 55, 116, 0, 3, 97, 97, 97, 120]

(请注意,这似乎只适用于最后 7 个字符相等的映射键)

从上面的输出中,您可以看到顺序在每次序列化往返之后继续变化。

我知道地图的内部顺序不能保证是一致的,我不依赖它,但我会假设在序列化往返之后,当地图本身没有改变时,序列化的字节将是相同的。

JDK 中的哪些具体更改导致了这种情况发生? (是 JDK 的 bug 吗?)

有没有办法让同一个 hashmap 始终获得相同的序列化字节? (不使用不同的顺序保留图)

【问题讨论】:

    标签: java serialization


    【解决方案1】:

    我希望能够获得一致的序列化数据。

    如果需要,则需要使用不同的数据结构。 HashMap 类不提供这些保证。

    在任何简单的哈希表中,观察到的条目顺序取决于:

    • 表格的大小,

    • 添加和删除元素的顺序,以及

    • hashcode() 函数返回的实际值。

    如果您基于哈希表编写了自定义Map,则您可以(理论上)在序列化/反序列化时控制前两个。但最后一个不在你的控制范围内。因此,如果您的其中一个键(例如)具有依赖于身份哈希码的哈希码,那么无论您如何序列化/反序列化,您都无法保留迭代顺序。

    在您的情况下,您似乎正在序列化/反序列化 HashMap&lt;String, String&gt;。这是理论上可以跨 Java 版本保留订单的一种情况。 (散列 Java 字符串的算法是指定的 ...)但是,我看不出您将如何使用HashMap 来实现它...没有在类私有内部数据中四处寻找结构。

    简而言之,如果您需要在序列化/反序列化过程中保留元素的顺序,请使用 LinkedHashMapTreeMap

    【讨论】:

      【解决方案2】:

      HashMap 没有任何可预测的顺序。因此,如果序列化改变了它恰好具有的顺序,这不是问题。请注意,在地图中进行任何更改(添加、删除)也会更改其顺序。

      如果广告订单很重要,那么您应该使用LinkedHashMap

      【讨论】:

      • 我知道地图的内部顺序不能保证是一致的,我不依赖它,但我会假设在序列化往返之后,当地图本身时,序列化的字节将是相同的没有改变。我希望能够获得一致的序列化数据。
      • @kazvictor 为什么需要一致的序列化数据?如果有必要,您可能已经在尝试一种可以以更好的方式重新完成的方法。
      • 地图有自己的序列化格式,避免占用过多带宽。当它被反序列化时,桶的重组方式不同。
      【解决方案3】:

      HashMap 被清楚地记录为无序。如果您依赖他们的命令,那么您已经做错了。

      【讨论】:

      • 我知道地图的内部顺序不能保证是一致的,我不依赖它,但我会假设在序列化往返之后,当地图本身时,序列化的字节将是相同的没有改变。我希望能够获得一致的序列化数据。
      • @kazvictor “我会假设在序列化往返之后,当地图本身没有改变时,序列化的字节将是相同的。”不,HashMap 不提供此类保证。散列桶使用随机和每个 JVM 调用的种子;这意味着相同的 HashMap 可以在同一台机器上从一个 JVM 调用不同地迭代到另一个。
      • 您不能假设 Javadoc 中没有明确说明的任何内容。
      • 而“我不依赖它”和“我想获得一致的序列化数据”这两个陈述是相互矛盾的。
      猜你喜欢
      • 2010-12-26
      • 2018-08-19
      • 2016-10-18
      • 1970-01-01
      • 2017-03-17
      • 1970-01-01
      • 2016-02-11
      • 2012-07-06
      • 1970-01-01
      相关资源
      最近更新 更多