5

我是编年史地图的新手。我正在尝试使用 Chronicle-map 对堆外映射进行建模,其中键是原始短数组,值是原始长数组。对于给定的地图,长数组值的最大大小是已知的。但是,我将有多个此类映射,每个映射对于长数组值可能具有不同的最大大小。我的问题与键和值的序列化/反序列化有关。

通过阅读文档,我了解到对于键,我可以使用值类型 ShortValue 并重用该接口的实现实例。关于值,我发现页面谈论DataAccess 和 SizeReader的页面给出了 byte[] 的示例,但我不确定如何使其适应 long[]。我还有一个额外的要求是,我需要在长数组中的任意索引处获取和设置值,而无需支付每次对整个值进行完全序列化/反序列化的成本。

所以我的问题是:如何在构建映射时对值类型进行建模,如果每个映射的最大大小已知并且我需要能够随机读写每次都没有序列化/反序列化整个值有效负载的索引?理想情况下,long[] 将直接在堆外进行编码/解码,而无需进行堆内中间转换为 byte[],并且编年史映射代码不会在运行时分配。谢谢你。

4

2 回答 2

3

首先,我建议使用某种LongList接口抽象来代替long[],这将更容易处理大小可变性,提供替代的享元实现等。

如果您只想读取/写入大型列表中的单个元素,则应使用高级上下文 API

/** This method is entirely garbage-free, deserialization-free, and thread-safe. */
void putOneValue(ChronicleMap<ShortValue, LongList> map, ShortValue key, int index,
        long element) {
    if (index < 0) throw throw new IndexOutOfBoundsException(...);
    try (ExternalMapQueryContext<ShortValue, LongList, ?> c = map.getContext(key)) {
        c.writeLock().lock(); // (1)
        MapEntry<ShortValue, LongList> entry = c.entry();
        if (entry != null) {
            Data<LongList> value = entry.value();
            BytesStore valueBytes = (BytesStore) value.bytes(); // (2)
            long valueBytesOffset = value.offset();
            long valueBytesSize = value.size();
            int valueListSize = (int) (valueBytesSize / Long.BYTES); // (3)
            if (index >= valueListSize) throw new IndexOutOfBoundsException(...);
            valueBytes.writeLong(valueBytesOffset + ((long) index) * Long.BYTES,
                element);
            ((ChecksumEntry) entry).updateChecksum(); // (4)
        } else {
            // there is no entry for the given key
            throw ...
        }
    }
}

笔记:

  1. 必须writeLock()从头获取,否则调用context.entry()方法时会自动获取readLock(),以后无法将读锁升级为写锁。请仔细阅读HashQueryContextjavadoc
  2. Data.bytes()正式返回RandomDataInput,但您可以确定(它在Data.bytes()javadoc 中指定)它实际上是(和BytesStore的组合)的一个实例。RandomDataInputRandomDataOutput
  3. 假设提供了正确的SizedReaderSizedWriter(或DataAccess)。请注意,使用了“字节/元素联合大小”技术,SizedReaderSizedWriterdoc section中给出的示例相同PointListSizeMarshaller。您可以LongListMarshaller基于该示例类。
  4. 此强制转换是指定的,请参阅ChecksumEntryjavadoc文档中关于校验和的部分。如果您有一个纯粹的内存中(非持久化)Chronicle Map,或者关闭了校验和,则可以省略此调用。

单元素读取的实现是类似的。

于 2018-02-06T23:49:37.723 回答
1

回答额外的问题:

我已经实现了一个 SizeReader+Writer。我需要 DataAccess 还是 SizedWriter 对于原始数组来说足够快?我查看了 ByteArrayDataAccess,但由于内部 HeapBytesStore 对 byte[]/ByteBuffers 如此特殊,尚不清楚如何将其移植到长数组中?

使用 ofDataAccess而不是SizedWriter允许在Map.put(key, value). 但是,如果在您的用例putOneValue()中(如上面的示例)是主要的查询类型,则不会有太大区别。如果Map.put(key, value)(和replace()等,即任何“全值写入”操作)很重要,仍然可以实现DataAccessfor LongList。它看起来像这样:

class LongListDataAccess implements DataAccess<LongList>, Data<LongList>,
        StatefulCopyable<LongListDataAccess> {
    transient ByteStore cachedBytes;
    transient boolean cachedBytesInitialized;
    transient LongList list;

    @Override public Data<LongList> getData(LongList list) {
        this.list = list;
        this.cachedBytesInitialized = false;
        return this;
    }

    @Override public long size() {
        return ((long) list.size()) * Long.BYTES;
    }

    @Override public void writeTo(RandomDataOutput target, long targetOffset) {
        for (int i = 0; i < list.size(); i++) {
            target.writeLong(targetOffset + ((long) i) * Long.BYTES), list.get(i));
        }
    }

    ...
}

为了提高效率,方法size()和方法writeTo()是关键。但是正确实现所有其他方法(我没有在这里写)也很重要。仔细阅读DataAccessData和javadocs,也非常注意教程中的UnderstandingCustom serialization checklistStatefulCopyableStatefulCopyableDataAccessSizedReader


读/写锁定是在同一台机器上的多个进程读取和写入之间进行调解还是在单个进程中进行调解?

跨进程是安全的,请注意该接口称为InterProcess ReadWriteUpdateLock


存储对象时,事先不知道可变大小,因为值会导致堆和持久文件中的碎片?

为键存储一次值,之后不更改值的大小(而不是删除键)不会导致外部碎片。更改值的大小或删除键可能会导致外部碎片。ChronicleMapBuilder.actualChunkSize()配置允许在外部和内部碎片之间进行交易。chunk 越大,外部碎片越少,但内部碎片越多。如果您的值明显大于页面大小(4 KB),您可以设置大到荒谬的块大小,并且仍然具有受页面大小限制的内部碎片,因为 Chronicle Map 能够利用 Linux 中的惰性页面分配功能。

于 2018-02-08T02:42:47.803 回答