为什么指定Map的初始容量会导致后续序列化给出不同的结果?
Why does specifying Map's initial capacity cause subsequent serializations to give different results?
我正在尝试比较 2 byte[]
,它们是同一对象的序列化结果:
- 1
byte[]
是通过序列化对象创建的
- 另一个通过反序列化第一个
byte[]
然后再次序列化它。
我不明白这两个数组有何不同。反序列化第一个 byte[]
应该重建原始对象,序列化该对象与序列化原始对象相同。所以,2 byte[]
应该是一样的。但是,在某些情况下,它们显然可能不同。
我正在序列化的对象 (State
) 包含另一个对象 (MapWrapper
) 的列表,而后者又包含一个集合。根据集合的不同,我从比较代码中得到不同的结果。
这是 MCVE:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Test {
public static void main(String[] args) {
State state = new State();
state.maps.add(new MapWrapper());
byte[] pBA = stateToByteArray(state);
State pC = byteArrayToState(pBA);
byte[] zero = stateToByteArray(pC);
System.out.println(Arrays.equals(pBA, zero)); // see output below
State pC2 = byteArrayToState(pBA);
byte[] zero2 = stateToByteArray(pC2);
System.out.println(Arrays.equals(zero2, zero)); // always true
}
public static byte[] stateToByteArray(State s) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(s);
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static State byteArrayToState(byte[] bytes) {
ObjectInputStream ois;
try {
ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return (State) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
class State implements Serializable {
private static final long serialVersionUID = 1L;
List<MapWrapper> maps = new ArrayList<>();
}
class MapWrapper implements Serializable {
private static final long serialVersionUID = 1L;
// Different options, choose one!
// List<Integer> ints = new ArrayList<>(); true
// List<Integer> ints = new ArrayList<>(3); true
// Map<String, Integer> map = new HashMap<>(); true
// Map<String, Integer> map = new HashMap<>(2); false
}
由于某些原因,如果MapWrapper
包含一个HashMap
(或LinkedHashMap
)并且被初始化为一个初始容量,序列化给出与序列化-反序列化-序列化不同的结果。
我添加了反序列化-序列化的第二次迭代,并与第一次迭代进行了比较。他们总是平等的。差异仅在第一次迭代后出现。
请注意,我必须创建一个 MapWrapper
并将其添加到 State
中的列表中,就像在 main
开始时所做的那样。
据我所知,初始容量只是一个性能参数。使用默认值或指定值不应更改行为或功能。
我正在使用 jdk1.8.0_25 和 Windows7.
为什么会这样?
HashMap
source code of readObject
中的以下行和注释解释了差异:
s.readInt(); // Read and ignore number of buckets
确实,查看字节的十六进制,区别在于数字 2(您配置的桶数)和数字 16(默认桶数)。我还没有检查过这个特定字节的含义;但考虑到这是唯一的区别,如果它是其他东西,那将是一个巧合。
<snip> 08 00 00 00 02 00 00 00 00 78 78 // Original
<snip> 08 00 00 00 10 00 00 00 00 78 78 // Deserialized+serialized.
^
我正在尝试比较 2 byte[]
,它们是同一对象的序列化结果:
- 1
byte[]
是通过序列化对象创建的 - 另一个通过反序列化第一个
byte[]
然后再次序列化它。
我不明白这两个数组有何不同。反序列化第一个 byte[]
应该重建原始对象,序列化该对象与序列化原始对象相同。所以,2 byte[]
应该是一样的。但是,在某些情况下,它们显然可能不同。
我正在序列化的对象 (State
) 包含另一个对象 (MapWrapper
) 的列表,而后者又包含一个集合。根据集合的不同,我从比较代码中得到不同的结果。
这是 MCVE:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Test {
public static void main(String[] args) {
State state = new State();
state.maps.add(new MapWrapper());
byte[] pBA = stateToByteArray(state);
State pC = byteArrayToState(pBA);
byte[] zero = stateToByteArray(pC);
System.out.println(Arrays.equals(pBA, zero)); // see output below
State pC2 = byteArrayToState(pBA);
byte[] zero2 = stateToByteArray(pC2);
System.out.println(Arrays.equals(zero2, zero)); // always true
}
public static byte[] stateToByteArray(State s) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(s);
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static State byteArrayToState(byte[] bytes) {
ObjectInputStream ois;
try {
ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return (State) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
class State implements Serializable {
private static final long serialVersionUID = 1L;
List<MapWrapper> maps = new ArrayList<>();
}
class MapWrapper implements Serializable {
private static final long serialVersionUID = 1L;
// Different options, choose one!
// List<Integer> ints = new ArrayList<>(); true
// List<Integer> ints = new ArrayList<>(3); true
// Map<String, Integer> map = new HashMap<>(); true
// Map<String, Integer> map = new HashMap<>(2); false
}
由于某些原因,如果MapWrapper
包含一个HashMap
(或LinkedHashMap
)并且被初始化为一个初始容量,序列化给出与序列化-反序列化-序列化不同的结果。
我添加了反序列化-序列化的第二次迭代,并与第一次迭代进行了比较。他们总是平等的。差异仅在第一次迭代后出现。
请注意,我必须创建一个 MapWrapper
并将其添加到 State
中的列表中,就像在 main
开始时所做的那样。
据我所知,初始容量只是一个性能参数。使用默认值或指定值不应更改行为或功能。
我正在使用 jdk1.8.0_25 和 Windows7.
为什么会这样?
HashMap
source code of readObject
中的以下行和注释解释了差异:
s.readInt(); // Read and ignore number of buckets
确实,查看字节的十六进制,区别在于数字 2(您配置的桶数)和数字 16(默认桶数)。我还没有检查过这个特定字节的含义;但考虑到这是唯一的区别,如果它是其他东西,那将是一个巧合。
<snip> 08 00 00 00 02 00 00 00 00 78 78 // Original
<snip> 08 00 00 00 10 00 00 00 00 78 78 // Deserialized+serialized.
^