我刚刚花了几个小时调试我的应用程序,我相信我偶然发现了一个(另一个o_O) Java .嗅嗅..。我希望不是,因为这将是可悲的:
我正在做以下几件事:
maskObjectOutputStream.writeObject(mask))mask中的其他标志预期结果:第二个序列化对象与第一个序列化对象不同(反映实例中的更改)
获得的结果:第二个序列化对象是第一个序列化对象的精确副本
守则:
enum MyEnum {
ONE, TWO
}
@Test
public void testEnumSetSerialize() throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream stream = new ObjectOutputStream(bos);
EnumSet<MyEnum> mask = EnumSet.noneOf(MyEnum.class);
mask.add(MyEnum.ONE);
mask.add(MyEnum.TWO);
System.out.println("First serialization: " + mask);
stream.writeObject(mask);
mask.clear();
System.out.println("Second serialization: " + mask);
stream.writeObject(mask);
stream.close();
ObjectInputStream istream = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
System.out.println("First deserialized " + istream.readObject());
System.out.println("Second deserialized " + istream.readObject());
}它打印:
第一次序列化:第一次,第二次序列化:[]第一次反序列化一次,第二次反序列化一次,第二次<<<<<<期望[]在这里!
我是不是不正确地使用EnumSet?我是否必须每次创建一个新实例,而不是清除它?
谢谢你的意见!
*更新*
我最初的想法是使用EnumSet作为掩码,以指示后面消息中哪些字段将出现或缺失,因此是一种带宽和cpu使用优化。这是非常错误的!一个EnumSet需要很长时间才能序列化,每个实例需要30 (!)字节!太空经济就这么多了:)
简而言之,虽然ObjectOutputStream对于原始类型非常快速(正如我在这里已经在一个小测试中指出的:https://stackoverflow.com/a/33753694),但是对于(特别是小的)对象来说,它是痛苦的、效率低下的。
因此,我通过创建由int支持的自己的EnumSet,以及直接序列化/反序列化int (而不是对象)来解决这个问题。
static class MyEnumSet<T extends Enum<T>> {
private int mask = 0;
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
return mask == ((MyEnumSet<?>) o).mask;
}
@Override
public int hashCode() {
return mask;
}
private MyEnumSet(int mask) {
this.mask = mask;
}
public static <T extends Enum<T>> MyEnumSet<T> noneOf(Class<T> clz) {
return new MyEnumSet<T>(0);
}
public static <T extends Enum<T>> MyEnumSet<T> fromMask(Class<T> clz, int mask) {
return new MyEnumSet<T>(mask);
}
public int mask() {
return mask;
}
public MyEnumSet<T> add(T flag) {
mask = mask | (1 << flag.ordinal());
return this;
}
public void clear() {
mask = 0;
}
}
private final int N = 1000000;
@Test
public void testSerializeMyEnumSet() throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream(N * 100);
ObjectOutputStream out = new ObjectOutputStream(bos);
List<MyEnumSet<TestEnum>> masks = Lists.newArrayList();
Random r = new Random(132477584521L);
for (int i = 0; i < N; i++) {
MyEnumSet<TestEnum> mask = MyEnumSet.noneOf(TestEnum.class);
for (TestEnum f : TestEnum.values()) {
if (r.nextBoolean()) {
mask.add(f);
}
}
masks.add(mask);
}
logger.info("Serializing " + N + " myEnumSets");
long tic = TicToc.tic();
for (MyEnumSet<TestEnum> mask : masks) {
out.writeInt(mask.mask());
}
TicToc.toc(tic);
out.close();
logger.info("Size: " + bos.size() + " (" + (bos.size() / N) + "b per object)");
logger.info("Deserializing " + N + " myEnumSets");
MyEnumSet<TestEnum>[] deserialized = new MyEnumSet[masks.size()];
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
tic = TicToc.tic();
for (int i = 0; i < deserialized.length; i++) {
deserialized[i] = MyEnumSet.fromMask(TestEnum.class, in.readInt());
}
TicToc.toc(tic);
Assert.assertArrayEquals(masks.toArray(), deserialized);
}它在序列化期间大约快130倍,在反序列化期间则快25倍.
MyEnumSets:
17/12/15 11:59:31 INFO -序列化1000000 myEnumSets 17/12/15 11:59:31信息-运行时间为0.019 s 17/12/15 11:59:31 INFO -大小: 4019539 (每个对象4b) 17/12/15 11:59:31 INFO -反序列化1000000 myEnumSets 17/12/15 11:59:31 INFO -运行时间为0.021 s
常规EnumSets:
17/12/15 11:59:48 INFO -序列化1000000 enumSets 17/12/15 11:59:51 INFO -运行时间为2.506 s 17/12/15 11:59:51 INFO -大小: 30691553 (每个对象30b) 17/12/15 11:59:51 INFO -去序列化1000000 enumSets 17/12/15 11:59:51 INFO -运行时间为0.489 s
但不太安全。例如,它不适用于超过32个条目的枚举。
如何确保枚举在创建MyEnumSet时的值小于32个?
发布于 2015-12-16 16:27:07
ObjectOutputStream序列化对对象的引用,并且在第一次发送对象时,实际对象。如果您修改了一个对象并再次发送它,那么ObjectOutputStream所做的就是再次发送对该对象的引用。
这有几个后果
解决这个问题并获得一些内存的方法是在每个完整的对象之后调用reset()。在调用flush()之前
重置将忽略任何已写入流的对象的状态。状态重置为与新的ObjectOutputStream相同。流中的当前点被标记为重置,因此相应的ObjectInputStream将在同一点重置。以前写入流的对象不会被引用为已经在流中。他们将再次被写到小溪上。
另一种方法是使用writeUnshared,但是这会将浅层不可共享性应用于顶层对象。在EnumSet的情况下,情况会有所不同,但是它包装的Enum[]仍然是共享的o_O。
将“未共享”对象写入ObjectOutputStream。此方法与writeObject相同,只不过它总是将给定的对象写入流中的一个新的唯一对象(而不是指向先前序列化的实例的反向引用)。
简而言之,这不是一个bug,而是预期的行为。
https://stackoverflow.com/questions/34317076
复制相似问题