当前位置:  开发笔记 > 编程语言 > 正文

EnumSet序列化

如何解决《EnumSet序列化》经验,为你挑选了1个好方法。

我刚刚失去了几个小时调试我的应用程序,我相信我偶然发现了一个(另一个o_O)Java bug ...嗅...我希望它不是,因为这会很难过:(

我正在做以下事情:

    mask使用一些标志创建EnumSet

    序列化(带ObjectOutputStream.writeObject(mask))

    清除并设置其中的一些其他标志 mask

    再次序列化

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

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

代码:

enum MyEnum {
    ONE, TWO
}

@Test
public void testEnumSetSerialize() throws Exception {           
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream stream = new ObjectOutputStream(bos);

    EnumSet 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> {
    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 > MyEnumSet noneOf(Class clz) {
        return new MyEnumSet(0);
    }

    public static > MyEnumSet fromMask(Class clz, int mask) {
        return new MyEnumSet(mask);
    }

    public int mask() {
        return mask;
    }

    public MyEnumSet 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> masks = Lists.newArrayList();

    Random r = new Random(132477584521L);
    for (int i = 0; i < N; i++) {
        MyEnumSet 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 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[] 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?



1> Peter Lawrey..:

ObjectOutputStream序列化对象的引用以及第一次发送对象时的实际对象.如果修改对象并再次发送它,则所有ObjectOutputStream都会再次发送对该对象的引用.

这有一些后果

如果修改对象,则不会看到这些修改

它必须保留对两端发送的每个对象的引用.这可能是一个微妙的内存泄漏.

这样做的原因是你可以序列化对象的图形而不是树.例如,指向B的A指向A.您只想发送一次A.

解决此问题并获取内存的方法是在每个完整对象之后调用reset().比如在打电话之前flush()

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

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

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

简而言之,这不是一个错误,而是预期的行为.


这是一个滑稽的声明.不反序列化同一个对象的目的是支持循环对象图的序列化,这是一个非常壮举.这是一个功能,而不是一个bug.
推荐阅读
LEEstarmmmmm
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有