首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >EnumSet序列化

EnumSet序列化
EN

Stack Overflow用户
提问于 2015-12-16 16:22:49
回答 1查看 1.7K关注 0票数 9

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

我正在做以下几件事:

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

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

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

守则:

代码语言:javascript
复制
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 (而不是对象)来解决这个问题。

代码语言:javascript
复制
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个?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-12-16 16:27:07

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

这有几个后果

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

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

重置将忽略任何已写入流的对象的状态。状态重置为与新的ObjectOutputStream相同。流中的当前点被标记为重置,因此相应的ObjectInputStream将在同一点重置。以前写入流的对象不会被引用为已经在流中。他们将再次被写到小溪上。

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

将“未共享”对象写入ObjectOutputStream。此方法与writeObject相同,只不过它总是将给定的对象写入流中的一个新的唯一对象(而不是指向先前序列化的实例的反向引用)。

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

票数 12
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34317076

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档