【发布时间】:2016-03-22 21:18:15
【问题描述】:
我刚刚在调试我的应用程序时浪费了几个小时,而且我相信我偶然发现了一个(另一个 o_O)Java 错误...嗅...我希望不是,因为这会很伤心:(
我正在做以下事情:
- 创建带有一些标志的 EnumSet
mask - 对其进行序列化(使用
ObjectOutputStream.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());
}
打印出来:
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 个?
【问题讨论】:
标签: java serialization enumset