如何使用Chronicle Map对long[]进行随机索引的序列化与反序列化?
解决Chronicle Map中short键+可变最大长度long数组值的序列化问题
嗨,作为Chronicle Map新手,碰到这种自定义值序列化的问题太正常了!我帮你拆解下键和值的处理方案,重点在值的自定义序列化上,因为每个Map的long数组最大长度不一样,得针对性处理~
一、short键的序列化:直接用内置支持
对于short类型的键,你根本不用自己写序列化器!Chronicle Map已经为基本类型的包装类(比如Short)提供了高效的内置KeyMarshaller,构建Map时直接指定键类型为Short.class就好,底层会自动完成序列化/反序列化,性能拉满还不用造轮子。
二、long数组值的自定义序列化:按Map的最大长度定制
这部分是核心,因为每个Map的long数组最大长度不同,而Chronicle Map的堆外存储要求值的序列化大小固定(这样才能预先分配内存空间)。所以我们需要为每个特定最大长度的Map,单独实现对应的ValueMarshaller<long[]>。
步骤1:实现自定义的固定长度long数组Marshaller
这个Marshaller要做三件事:
- 写入时先存实际数组长度,再写入元素(不足最大长度的补0,保证总字节数固定)
- 读取时先读实际长度,再读取对应数量的元素,跳过补0的字节
- 告诉Chronicle Map这个值的固定字节大小
代码示例如下:
import net.openhft.chronicle.bytes.Bytes; import net.openhft.chronicle.map.ValueMarshaller; public class FixedLengthLongArrayMarshaller implements ValueMarshaller<long[]> { private final int maxArrayLength; // 构造时传入当前Map允许的数组最大长度 public FixedLengthLongArrayMarshaller(int maxArrayLength) { this.maxArrayLength = maxArrayLength; } @Override public void write(Bytes out, long[] value) { // 先校验数组长度,避免超过当前Map的上限 if (value.length > maxArrayLength) { throw new IllegalArgumentException( String.format("数组长度不能超过当前Map的最大值%s,当前传入%s", maxArrayLength, value.length) ); } // 写入实际数组长度(4字节),反序列化时靠它知道要读多少元素 out.writeInt(value.length); // 写入数组元素,不足最大长度的补0,保证总字节数固定 for (int i = 0; i < maxArrayLength; i++) { out.writeLong(i < value.length ? value[i] : 0L); } } @Override public long[] read(Bytes in, long[] using) { int actualLength = in.readInt(); // 复用传入的using数组(如果足够大),减少GC开销 long[] result = (using != null && using.length >= actualLength) ? using : new long[actualLength]; // 读取实际长度的元素 for (int i = 0; i < actualLength; i++) { result[i] = in.readLong(); } // 跳过剩下的补0字节,保证指针位置正确 in.skip((long) (maxArrayLength - actualLength) * 8); return result; } @Override public long fixedSize() { // 固定字节数:4字节(实际长度) + 最大长度*8字节(每个long占8字节) return 4 + (long) maxArrayLength * 8; } }
步骤2:为不同最大长度的Map构建实例
每个Map对应自己的Marshaller实例,传入对应的最大数组长度就行:
import net.openhft.chronicle.map.ChronicleMap; import net.openhft.chronicle.map.ChronicleMapBuilder; public class ChronicleMapExample { public static void main(String[] args) { // 第一个Map:允许的long数组最大长度为10 int maxLen1 = 10; ChronicleMap<Short, long[]> map1 = ChronicleMapBuilder .of(Short.class, long[].class) .valueMarshaller(new FixedLengthLongArrayMarshaller(maxLen1)) .entries(1000) // 根据你的业务需求设置预计的条目数 .create(); // 第二个Map:允许的long数组最大长度为20 int maxLen2 = 20; ChronicleMap<Short, long[]> map2 = ChronicleMapBuilder .of(Short.class, long[].class) .valueMarshaller(new FixedLengthLongArrayMarshaller(maxLen2)) .entries(1000) .create(); // 测试写入读取 map1.put((short) 1, new long[]{1L, 2L, 3L}); long[] result = map1.get((short) 1); // 输出:[1, 2, 3] System.out.println(java.util.Arrays.toString(result)); } }
三、额外注意事项
- 固定大小是核心:Chronicle Map的堆外存储依赖固定大小的键值,所以
fixedSize()方法一定要返回正确的字节数,否则会出现内存 corruption 或者读取错误。 - 校验数组长度:写入时一定要检查数组长度不超过当前Map的最大值,避免数据被截断或者破坏堆外结构。
- 复用数组提升性能:
read方法中的using参数是用来复用对象的,尽量传入已有的数组,减少垃圾回收的压力,尤其是高并发场景下。
内容的提问来源于stack exchange,提问作者junkie
相关产品推荐
相关产品推荐

