gpt4 book ai didi

java - EnumSet 序列化

转载 作者:塔克拉玛干 更新时间:2023-11-03 03:14:58 26 4
gpt4 key购买 nike

我刚刚花了几个小时调试我的应用程序,而且我相信我偶然发现了一个(另一个 o_O)Java 错误...嗅探...我希望不是,因为这会让人难过:(

我正在做以下事情:

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

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

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

代码:

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 倍......

我的枚举集:

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

之前

Reset will disregard the state of any objects already written to the stream. The state is reset to be the same as a new ObjectOutputStream. The current point in the stream is marked as reset so the corresponding ObjectInputStream will be reset at the same point. Objects previously written to the stream will not be referred to as already being in the stream. They will be written to the stream again.

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

Writes an "unshared" object to the ObjectOutputStream. This method is identical to writeObject, except that it always writes the given object as a new, unique object in the stream (as opposed to a back-reference pointing to a previously serialized instance).

简而言之,不,这不是错误,而是预期的行为。

关于java - EnumSet 序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34317076/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com