Apache Ignite:反序列化后在 Hashmap 中找不到对象
Apache Ignite: Object cannot be found in Hashmap after deserialization
我在使用 Apache Ignite Serialization/Deserialization 时遇到了与字段反序列化顺序相关的问题。我需要在 Ignite 缓存中放置一个 "B" 的实例:
public class A {
private final String name;
public A(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class B extends A {
private Map<B, String> mapOfB;
public B(String name) {
super(name);
mapOfB = new HashMap<>();
}
public void addB(B newB, String someString) {
mapOfB.put(newB, someString);
}
public Map<B, String> getMap() {
return mapOfB;
}
@Override
public boolean equals(Object obj) {
if( obj != null && obj instanceof B) {
if(this.getName() == null && ((B) obj).getName() == null && this == obj) {
return true;
} else if(this.getName().equals(((B) obj).getName())) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return this.getName()==null? System.identityHashCode(this):this.getName().hashCode();
}
}
如果我运行下面的代码:
public static void main(String[] args) {
// write your code here
B b1 = new B("first");
b1.addB(b1, "some first string");
B b2 = new B("second");
b1.addB(b2, "some second string");
// init Ignite configuration
// force java.util.Hashtable to be binary serialized,
// it prevents infinite recursion and other problems
// occurring with the Optimized Serializer
IgniteConfiguration cfg = new IgniteConfiguration();
BinaryConfiguration binConf = new BinaryConfiguration();
Collection<String> binClassNames = new LinkedList<>();
binClassNames.add("java.util.Hashtable");
binConf.setClassNames(binClassNames);
cfg.setBinaryConfiguration(binConf);
Ignition.start(cfg);
// put b1 in cache
IgniteCache cache = Ignition.ignite().getOrCreateCache("MyCache");
cache.put(b1.hashCode(), b1);
//get b1 from cache
B b1FromCache= (B) cache.get(b1.hashCode());
// print map values
System.out.println("b1 map value: " + b1.getMap().get(b1));
System.out.println("b1 from cache map value: " + b1FromCache.getMap().get(b1));
}
输出是
b1 映射值:一些第一个字符串
b1 来自缓存映射值:null
问题是子字段在父字段之前被反序列化,所以当 Ignite 反序列化 B 时,它首先创建一个空的 B 对象(具有 null "name" 和 "mapOfB"),然后它尝试反序列化 mapOfB。它创建哈希表,然后反序列化它包含的每个对象以填充它。
上面例子中的b2是没有问题的,因为在反序列化的时候还不存在对b2的引用,所以创建了一个新的b2对象,填充了b2字段(包括"name"字段),然后将其添加到具有正确哈希值的哈希图中。
但是对于 b1,反序列化已开始,因此该对象已存在于 Ignit 的反序列化对象映射中,但名称为空(b1 的反序列化正在进行中),并且具有使用此空名称计算的 hashCode。 Hashtable 将 b1 与当时计算的 hashCode 放在一起,因此当我们试图在最后的 map 中查找具有非 null 名称的 b1 时,它找不到。
我无法更改 A 和 B 类 以及这些对象的创建方式(Hashmap 的填充方式,...)因此必须通过更改序列化来解决。有没有一种简单的方法可以做到这一点?
备注:实际代码比这复杂得多,实际 B 和 Hashmap 之间可能 类。
你对这种行为的原因是正确的。 mapOfB
字段在 name
字段之前被反序列化,并且 hashCode()
取决于该名称。 B
的字段在您将其作为键放入地图后发生了变化,因此 hashCode
发生了变化。
我建议您更改数据模型,但由于您不能,这里有另一个选择... OptimizedMarshaller
地图似乎没有问题,因为它使用简单 Java 序列化。但是您将无法使用 BinaryObject 抽象和一些其他功能。
以下是启用 OptimizedMarshaller 的方法:
OptimizedMarshaller marshaller = new OptimizedMarshaller();
cfg.setMarshaller(marshaller);
如果您存储未实现 Serializable
接口的值,那么您可能需要适当地配置它:
marshaller.setRequireSerializable(false);
但请注意,禁用 requireSerializable
标志可能会对序列化性能产生负面影响。
我在使用 Apache Ignite Serialization/Deserialization 时遇到了与字段反序列化顺序相关的问题。我需要在 Ignite 缓存中放置一个 "B" 的实例:
public class A {
private final String name;
public A(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class B extends A {
private Map<B, String> mapOfB;
public B(String name) {
super(name);
mapOfB = new HashMap<>();
}
public void addB(B newB, String someString) {
mapOfB.put(newB, someString);
}
public Map<B, String> getMap() {
return mapOfB;
}
@Override
public boolean equals(Object obj) {
if( obj != null && obj instanceof B) {
if(this.getName() == null && ((B) obj).getName() == null && this == obj) {
return true;
} else if(this.getName().equals(((B) obj).getName())) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return this.getName()==null? System.identityHashCode(this):this.getName().hashCode();
}
}
如果我运行下面的代码:
public static void main(String[] args) {
// write your code here
B b1 = new B("first");
b1.addB(b1, "some first string");
B b2 = new B("second");
b1.addB(b2, "some second string");
// init Ignite configuration
// force java.util.Hashtable to be binary serialized,
// it prevents infinite recursion and other problems
// occurring with the Optimized Serializer
IgniteConfiguration cfg = new IgniteConfiguration();
BinaryConfiguration binConf = new BinaryConfiguration();
Collection<String> binClassNames = new LinkedList<>();
binClassNames.add("java.util.Hashtable");
binConf.setClassNames(binClassNames);
cfg.setBinaryConfiguration(binConf);
Ignition.start(cfg);
// put b1 in cache
IgniteCache cache = Ignition.ignite().getOrCreateCache("MyCache");
cache.put(b1.hashCode(), b1);
//get b1 from cache
B b1FromCache= (B) cache.get(b1.hashCode());
// print map values
System.out.println("b1 map value: " + b1.getMap().get(b1));
System.out.println("b1 from cache map value: " + b1FromCache.getMap().get(b1));
}
输出是
b1 映射值:一些第一个字符串
b1 来自缓存映射值:null
问题是子字段在父字段之前被反序列化,所以当 Ignite 反序列化 B 时,它首先创建一个空的 B 对象(具有 null "name" 和 "mapOfB"),然后它尝试反序列化 mapOfB。它创建哈希表,然后反序列化它包含的每个对象以填充它。
上面例子中的b2是没有问题的,因为在反序列化的时候还不存在对b2的引用,所以创建了一个新的b2对象,填充了b2字段(包括"name"字段),然后将其添加到具有正确哈希值的哈希图中。
但是对于 b1,反序列化已开始,因此该对象已存在于 Ignit 的反序列化对象映射中,但名称为空(b1 的反序列化正在进行中),并且具有使用此空名称计算的 hashCode。 Hashtable 将 b1 与当时计算的 hashCode 放在一起,因此当我们试图在最后的 map 中查找具有非 null 名称的 b1 时,它找不到。
我无法更改 A 和 B 类 以及这些对象的创建方式(Hashmap 的填充方式,...)因此必须通过更改序列化来解决。有没有一种简单的方法可以做到这一点?
备注:实际代码比这复杂得多,实际 B 和 Hashmap 之间可能 类。
你对这种行为的原因是正确的。 mapOfB
字段在 name
字段之前被反序列化,并且 hashCode()
取决于该名称。 B
的字段在您将其作为键放入地图后发生了变化,因此 hashCode
发生了变化。
我建议您更改数据模型,但由于您不能,这里有另一个选择... OptimizedMarshaller
地图似乎没有问题,因为它使用简单 Java 序列化。但是您将无法使用 BinaryObject 抽象和一些其他功能。
以下是启用 OptimizedMarshaller 的方法:
OptimizedMarshaller marshaller = new OptimizedMarshaller();
cfg.setMarshaller(marshaller);
如果您存储未实现 Serializable
接口的值,那么您可能需要适当地配置它:
marshaller.setRequireSerializable(false);
但请注意,禁用 requireSerializable
标志可能会对序列化性能产生负面影响。