EnumSet serialization



我刚刚花了几个小时调试我的应用程序,我相信我偶然发现了一个(另一个o_o)Java错误。。。闻我希望不是,因为这将是可悲的:(

我正在做以下事情:

  1. 使用某些标志创建EnumSet mask
  2. 将其序列化(使用ObjectOutputStream.writeObject(mask)
  3. 清除并设置mask中的一些其他标志
  4. 再次序列化

预期结果:第二个序列化对象与第一个不同(反映实例中的变化)

获得的结果:第二个序列化对象是第一个的精确副本

代码:

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());
}

它打印:

First serialization: [ONE, TWO]
Second serialization: []
First deserialized [ONE, TWO]
Second deserialized [ONE, TWO]  <<<<<< Expecting [] here!!!!

我是否错误地使用了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 - Serializing 1000000 myEnumSets
17/12/15 11:59:31 INFO - Elapsed time is 0.019 s
17/12/15 11:59:31 INFO - Size: 4019539 (4b per object)
17/12/15 11:59:31 INFO - Deserializing 1000000 myEnumSets
17/12/15 11:59:31 INFO - Elapsed time is 0.021 s

正则枚举集:

17/12/15 11:59:48 INFO - Serializing 1000000 enumSets
17/12/15 11:59:51 INFO - Elapsed time is 2.506 s
17/12/15 11:59:51 INFO - Size: 30691553 (30b per object)
17/12/15 11:59:51 INFO - Deserializing 1000000 enumSets
17/12/15 11:59:51 INFO - Elapsed time is 0.489 s

不过它不那么安全。例如,它不适用于超过32个条目的枚举。

如何确保在创建MyEnumSet时枚举的值少于32?

ObjectOutputStream序列化对对象的引用,并且在第一次发送对象时是实际对象。如果修改一个对象并再次发送,ObjectOutputStream所做的就是再次向该对象发送引用

这有一些后果

  • 如果修改对象,则不会看到这些修改
  • 它必须在两端保留对曾经发送过的每个对象的引用。这可能是一个细微的内存泄漏
  • 这样做的原因是,您可以序列化对象的图,而不是树。A指向B,B指向A。你只想发送一次A

解决这个问题并获得一些内存的方法是在每个完整的对象之后调用reset()。例如,在调用flush() 之前

重置将忽略已写入流的任何对象的状态。该状态将重置为与新的ObjectOutputStream相同。流中的当前点被标记为重置,因此相应的ObjectInputStream将在同一点重置。先前写入流的对象将不会被称为已经在流中。它们将再次被写入流中。

另一种方法是使用writeUnshared,但这会对顶级对象应用较浅的非共享性。在EnumSet的情况下,它将有所不同,但是它所封装的Enum[]仍然是共享的o_o

将"非共享"对象写入ObjectOutputStream。此方法与writeObject相同,只是它总是将给定对象作为流中新的、唯一的对象写入(而不是指向以前序列化的实例的反向引用)。

简而言之,不,这不是一个bug,而是预期的行为。

相关内容

  • 没有找到相关文章

最新更新