Сериализация туда-обратно хеш-карты не сохраняет порядок

1

Я заметил, что с недавней версией 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?)

Есть ли способ последовательно получать одни и те же сериализованные байты для одного и того же хэшмапа? (без использования другой сохраняющей порядок карты)

Теги:
serialization

3 ответа

3
Лучший ответ

HashMaps четко документированы как неупорядоченные. Если вы полагаетесь на их заказ, вы уже делаете что-то неправильно.

  • 0
    Я понимаю, что внутренний порядок карты не гарантированно согласован, и я не полагаюсь на него, но я бы предположил, что после циклических сериализаций сериализованные байты будут идентичны, если сама карта не изменилась. Я хотел бы иметь возможность получать последовательные сериализованные данные.
  • 0
    @kazvictor «Я бы предположил, что после циклических сериализаций сериализованные байты будут идентичны, если сама карта не изменилась». Нет, HashMap предоставляет таких гарантий. Хеш-контейнеры используют начальное число, которое является случайным и для каждого вызова JVM; это означает, что один и тот же HashMap может повторяться по-разному от одного вызова JVM к другому на той же машине.
Показать ещё 2 комментария
5

У HashMap нет предсказуемого порядка. Таким образом, это не проблема, если сериализация изменяет порядок, который она имеет. Обратите внимание, что выполнение любого изменения (добавление, удаление) на карте также изменит его порядок.

Если порядок вставки имеет значение, тогда вы должны использовать LinkedHashMap.

  • 0
    Я понимаю, что внутренний порядок карты не гарантированно согласован, и я не полагаюсь на него, но я бы предположил, что после циклических сериализаций сериализованные байты будут идентичны, если сама карта не изменилась. Я хотел бы иметь возможность получать последовательные сериализованные данные.
  • 0
    @kazvictor Зачем вам нужны последовательные сериализованные данные? Если это необходимо, вы, возможно, уже пробуете подход, который можно было бы сделать лучше.
Показать ещё 2 комментария
1

Я хотел бы иметь возможность получать последовательные сериализованные данные.

Если вам это нужно, вам нужно будет использовать другую структуру данных. Класс HashMap не дает этих гарантий.

В любой простой хеш-таблице наблюдаемый порядок записей зависит от:

  • размер стола,

  • порядок, в котором элементы добавляются и удаляются, и

  • фактические значения, возвращаемые функцией hashcode().

Первые два, которые вы могли бы (теоретически) контролировать при сериализации/десериализации, если вы написали пользовательскую Map на основе хеш-таблиц. Но последний находится вне вашего контроля. Поэтому, если у одного из ваших ключей есть (например) хеш-код, который зависит от хэш-кода идентификатора, тогда вы не можете сохранить порядок итераций... независимо от того, как вы сериализуете/десериализуете.

В вашем случае вы представляете собой сериализацию/десериализацию HashMap<String, String>. Это один из случаев, когда сохранение порядка теоретически возможно на Java-версиях. (Указан алгоритм для хэширования строки Java...) Однако я не могу понять, как вы могли бы достичь этого, используя HashMap..., не дожидаясь обмана в частных частных структурах данных классов.

Короче говоря, используйте LinkedHashMap или TreeMap если вам нужен порядок элементов для сохранения в сериализации/десериализации.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню