ChronicleMap无法直接更新值?求可行替代解决方案
ChronicleMap无法直接修改get()返回对象属性的原因及解决方案
你的判断完全正确。ChronicleMap采用堆外内存存储,调用get()时会把堆外的对象数据反序列化复制到堆内的新对象实例,你修改这个堆内副本的属性,并不会同步到堆外的实际存储中,所以必须通过put()/computeIfPresent()写回才能生效。
以下是几种可行的解决方案:
解决方案1:使用ChronicleMap特有的update()方法
ChronicleMap提供了update(K key, Consumer<V> updater)方法,它会直接在堆外存储的对象上下文里执行修改操作,不需要把整个对象拉回堆内再写回,效率更高,也符合你“直接修改属性”的需求。
修改你的测试代码如下:
@Test public void test(){ try(Map<Integer, MyClass> map = ChronicleMap .of(Integer.class, MyClass.class) .valueMarshaller(/* my marshaller */) .averageValue(/* average value */) .entries(/* number of entries */) .create()){ // 创建ChronicleMap条目 MyClass a = new MyClass(); map.put(0, a); // 直接修改堆外存储的对象属性 SomeState modified = new SomeState(/* some params */); map.update(0, myClass -> myClass.setState(modified)); // 测试通过 assertEquals(modified, map.get(0).getState()); } }
update()方法会自动处理锁和堆外数据的同步,不需要手动调用put()。
解决方案2:自定义MutableValueMarshaller
如果你的MyClass是可变对象,可以实现MutableValueMarshaller,让ChronicleMap在get()时返回一个直接关联堆外存储的代理对象,这样你修改代理对象的属性时,会直接同步到堆外存储。
核心实现思路:
- 自定义
MutableValueMarshaller,在read()方法中返回代理对象,该代理的setter方法会直接修改堆外的字节数据 - 这种方式需要手动处理序列化/反序列化的细节,适合对性能要求极高的场景
示例核心逻辑代码:
public class MyClassMarshaller implements MutableValueMarshaller<MyClass> { @Override public void write(MyClass value, Bytes out, WriteContext context) { // 序列化逻辑:将MyClass的state写入堆外Bytes } @Override public MyClass read(Bytes in, long size, ReadContext context) { // 返回代理对象,setState时直接修改堆外Bytes中的数据 return new MyClass() { @Override public void setState(SomeState state) { in.writePosition(...); // 定位到state在堆外的存储位置 // 将新的state序列化到堆外Bytes的对应位置 } }; } // 实现接口要求的其他必要方法 }
使用该Marshaller创建ChronicleMap后,调用map.get(0).setState(modified)就会直接修改堆外存储,无需额外调用put()。
解决方案3:优化computeIfPresent()的使用
如果场景允许,可以用computeIfPresent()简化代码,它会自动处理“获取对象-修改-写回”的完整流程:
map.computeIfPresent(0, (key, value) -> { value.setState(modified); return value; });
这种方式本质还是将对象拉回堆内修改后写回,但代码更简洁,适合不需要极致性能的场景。
内容的提问来源于stack exchange,提问作者RobPd
相关产品推荐
相关产品推荐

