有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

哈希映射的java序列化往返不会保留顺序

我注意到,在最新版本的Java(1.7.0_51)中,hashmap的序列化和反序列化不再保持hashmap中元素的顺序。请参见以下示例:

@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获取相同的序列化字节?(不使用不同的保序映射)


共 (3) 个答案

  1. # 1 楼答案

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

    如果插入顺序很重要,那么应该使用^{}

  2. # 2 楼答案

    I would like to be able to get consistent serialized data.

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

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

    • 桌子的大小,

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

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

    如果基于哈希表编写自定义Map,那么在序列化/反序列化时(理论上)可以控制前两个。但最后一个是你无法控制的。因此,如果你的一个密钥(例如)有一个依赖于身份哈希码的哈希码,那么你就不能保留迭代顺序。。。无论你如何序列化/反序列化

    在您的例子中,您似乎正在序列化/反序列化一个HashMap<String, String>。这是一种在Java版本中理论上可以保持顺序的情况。(对Java字符串进行散列的算法是指定的。)然而,我看不出你如何使用HashMap实现它。。。缺少在类中挖掘私有内部数据结构

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

  3. # 3 楼答案

    散列图清楚地记录为无序。如果你依赖他们的命令,你已经做错了