Я заметил, что с недавней версией 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?)
Есть ли способ последовательно получать одни и те же сериализованные байты для одного и того же хэшмапа? (без использования другой сохраняющей порядок карты)
HashMaps четко документированы как неупорядоченные. Если вы полагаетесь на их заказ, вы уже делаете что-то неправильно.
У HashMap нет предсказуемого порядка. Таким образом, это не проблема, если сериализация изменяет порядок, который она имеет. Обратите внимание, что выполнение любого изменения (добавление, удаление) на карте также изменит его порядок.
Если порядок вставки имеет значение, тогда вы должны использовать LinkedHashMap
.
Я хотел бы иметь возможность получать последовательные сериализованные данные.
Если вам это нужно, вам нужно будет использовать другую структуру данных. Класс HashMap
не дает этих гарантий.
В любой простой хеш-таблице наблюдаемый порядок записей зависит от:
размер стола,
порядок, в котором элементы добавляются и удаляются, и
фактические значения, возвращаемые функцией hashcode()
.
Первые два, которые вы могли бы (теоретически) контролировать при сериализации/десериализации, если вы написали пользовательскую Map
на основе хеш-таблиц. Но последний находится вне вашего контроля. Поэтому, если у одного из ваших ключей есть (например) хеш-код, который зависит от хэш-кода идентификатора, тогда вы не можете сохранить порядок итераций... независимо от того, как вы сериализуете/десериализуете.
В вашем случае вы представляете собой сериализацию/десериализацию HashMap<String, String>
. Это один из случаев, когда сохранение порядка теоретически возможно на Java-версиях. (Указан алгоритм для хэширования строки Java...) Однако я не могу понять, как вы могли бы достичь этого, используя HashMap
..., не дожидаясь обмана в частных частных структурах данных классов.
Короче говоря, используйте LinkedHashMap
или TreeMap
если вам нужен порядок элементов для сохранения в сериализации/десериализации.
HashMap
предоставляет таких гарантий. Хеш-контейнеры используют начальное число, которое является случайным и для каждого вызова JVM; это означает, что один и тот жеHashMap
может повторяться по-разному от одного вызова JVM к другому на той же машине.