168 lines
7.9 KiB
JSON
168 lines
7.9 KiB
JSON
|
|
{
|
|||
|
|
"topic": "heavykeeper-topk",
|
|||
|
|
"type": "single_choice",
|
|||
|
|
"schema_version": "1.0.0",
|
|||
|
|
"generated": "2026-09-09T21:42:00+08:00",
|
|||
|
|
"questions": [
|
|||
|
|
{
|
|||
|
|
"id": "sc-001",
|
|||
|
|
"type": "single_choice",
|
|||
|
|
"difficulty": 2,
|
|||
|
|
"tags": [
|
|||
|
|
"heavykeeper-topk",
|
|||
|
|
"heavykeeper"
|
|||
|
|
],
|
|||
|
|
"question": "HeavyKeeper 算法的核心设计目标是什么?",
|
|||
|
|
"options": {
|
|||
|
|
"A": "对所有元素进行精确频率计数",
|
|||
|
|
"B": "从海量数据流中高效探测 Top-K 热点 Key",
|
|||
|
|
"C": "替代 Bloom Filter 实现集合成员判定",
|
|||
|
|
"D": "对数据流进行无损压缩存储"
|
|||
|
|
},
|
|||
|
|
"answer": "B",
|
|||
|
|
"explanation": "HeavyKeeper 是一种面向流式数据的 Top-K 热 Key 探测算法,其核心设计目标是在有限内存下,从海量数据流中高效、准确地找出频率最高的 K 个 Key。它不是做全量精确计数(A),也不替代 Bloom Filter(C),更不是通用压缩(D)。其名字中的 'Heavy' 直接指向对热点(heavy hitter)的检测。",
|
|||
|
|
"source": null,
|
|||
|
|
"related": []
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "sc-002",
|
|||
|
|
"type": "single_choice",
|
|||
|
|
"difficulty": 2,
|
|||
|
|
"tags": [
|
|||
|
|
"heavykeeper-topk",
|
|||
|
|
"heavykeeper"
|
|||
|
|
],
|
|||
|
|
"question": "HeavyKeeper 中 depth 参数(例如 depth=5)的含义是什么?",
|
|||
|
|
"options": {
|
|||
|
|
"A": "每个 Key 最多被计数 5 次",
|
|||
|
|
"B": "数据结构维护 5 层独立的桶数组,每层独立散列",
|
|||
|
|
"C": "Top-K 排序时保留前 5 个候选",
|
|||
|
|
"D": "衰减淘汰的最大轮数为 5"
|
|||
|
|
},
|
|||
|
|
"answer": "B",
|
|||
|
|
"explanation": "depth 表示 HeavyKeeper 的行数(层数)。整个数据结构是 depth × width 的二维桶矩阵(Bucket[][]),每一层使用相同的哈希算法但独立维护一组桶。一个 Key 会被散列到每一层的对应桶中,查询时取各层计数的最大值作为频率估计。多层设计通过冗余降低误判概率。",
|
|||
|
|
"source": null,
|
|||
|
|
"related": []
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "sc-003",
|
|||
|
|
"type": "single_choice",
|
|||
|
|
"difficulty": 3,
|
|||
|
|
"tags": [
|
|||
|
|
"heavykeeper-topk",
|
|||
|
|
"heavykeeper"
|
|||
|
|
],
|
|||
|
|
"question": "在 HeavyKeeper 的 add() 方法中,当某个桶已有指纹不同的其他 Key(即发生冲突)时,算法如何处理?",
|
|||
|
|
"options": {
|
|||
|
|
"A": "直接覆盖桶中原有指纹,计数重置",
|
|||
|
|
"B": "将两个 Key 的频率取平均值存入桶中",
|
|||
|
|
"C": "按指数衰减概率递减桶的计数,有机会腾空桶后替换指纹",
|
|||
|
|
"D": "将冲突 Key 溢出到相邻桶中"
|
|||
|
|
},
|
|||
|
|
"answer": "C",
|
|||
|
|
"explanation": "HeavyKeeper 面对冲突时采用概率衰减淘汰策略:对于 increment 次尝试,每次以 lookupTable[bucket.count] 的概率(即约 0.92^count)递减桶计数。桶计数越大,衰减概率越低,热 Key 越难被淘汰。如果桶计数被减到 0,则腾空该桶并写入新 Key 的指纹和计数。这与 CMS 的简单计数累加形成鲜明对比。",
|
|||
|
|
"source": null,
|
|||
|
|
"related": []
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "sc-004",
|
|||
|
|
"type": "single_choice",
|
|||
|
|
"difficulty": 3,
|
|||
|
|
"tags": [
|
|||
|
|
"heavykeeper-topk",
|
|||
|
|
"cms"
|
|||
|
|
],
|
|||
|
|
"question": "与 Count-Min Sketch (CMS) 相比,HeavyKeeper 在处理冷 Key 冲突时的关键优势是什么?",
|
|||
|
|
"options": {
|
|||
|
|
"A": "HeavyKeeper 使用更多的哈希函数,冲突概率更低",
|
|||
|
|
"B": "HeavyKeeper 的冷 Key 会通过概率衰减被自动淘汰,不会持续污染热 Key 的计数",
|
|||
|
|
"C": "HeavyKeeper 不会产生任何误判",
|
|||
|
|
"D": "HeavyKeeper 使用更大的桶数组,冲突概率更低"
|
|||
|
|
},
|
|||
|
|
"answer": "B",
|
|||
|
|
"explanation": "CMS 的致命缺点是:冷 Key 与热 Key 哈希到同一位置时,冷 Key 的计数会累加到热 Key 上,且永远不会减少,导致热 Key 频率被持续高估(污染)。HeavyKeeper 通过概率衰减机制,冷 Key(频率低)对应的桶计数小,衰减概率高,容易被清零淘汰,从而避免对热 Key 的长期污染。",
|
|||
|
|
"source": null,
|
|||
|
|
"related": []
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "sc-005",
|
|||
|
|
"type": "single_choice",
|
|||
|
|
"difficulty": 3,
|
|||
|
|
"tags": [
|
|||
|
|
"heavykeeper-topk",
|
|||
|
|
"heavykeeper"
|
|||
|
|
],
|
|||
|
|
"question": "HeavyKeeper 中 lookupTable 数组预计算 0.92^i 的主要目的是什么?",
|
|||
|
|
"options": {
|
|||
|
|
"A": "用于对桶计数进行归一化处理",
|
|||
|
|
"B": "避免运行时反复调用 Math.pow(),用查表法加速指数衰减概率的计算",
|
|||
|
|
"C": "作为桶计数的上界阈值",
|
|||
|
|
"D": "用于计算 Top-K 的排序分数"
|
|||
|
|
},
|
|||
|
|
"answer": "B",
|
|||
|
|
"explanation": "lookupTable 是一个预计算的查找表,存储了 0.92^0, 0.92^1, ..., 0.92^255 的值。在 add() 的衰减逻辑中,每次需要计算衰减概率 decay = 0.92^count。如果每次用 Math.pow() 计算,性能开销很大。通过预计算并查表,将指数运算降为 O(1) 数组访问,显著提升吞吐量。",
|
|||
|
|
"source": null,
|
|||
|
|
"related": []
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "sc-006",
|
|||
|
|
"type": "single_choice",
|
|||
|
|
"difficulty": 4,
|
|||
|
|
"tags": [
|
|||
|
|
"heavykeeper-topk",
|
|||
|
|
"heavykeeper"
|
|||
|
|
],
|
|||
|
|
"question": "HeavyKeeper 的 fading() 方法对所有桶执行 `bucket.count = bucket.count >> 1`,这种右移操作的实际效果是?",
|
|||
|
|
"options": {
|
|||
|
|
"A": "将计数除以 2 并向上取整",
|
|||
|
|
"B": "将计数除以 2 并向下取整(等价于整数除法)",
|
|||
|
|
"C": "将计数乘以 2",
|
|||
|
|
"D": "将计数清零"
|
|||
|
|
},
|
|||
|
|
"answer": "B",
|
|||
|
|
"explanation": "Java 中的右移运算符 `>>` 对正整数等价于整数除以 2 并向下取整。例如:5 >> 1 = 2,7 >> 1 = 3,1 >> 1 = 0。fading() 利用这一特性实现全局衰减,让所有桶的计数减半,模拟时间窗口滑动的效果,使算法能适应数据流分布的变化。",
|
|||
|
|
"source": null,
|
|||
|
|
"related": []
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "sc-007",
|
|||
|
|
"type": "single_choice",
|
|||
|
|
"difficulty": 4,
|
|||
|
|
"tags": [
|
|||
|
|
"heavykeeper-topk",
|
|||
|
|
"heavykeeper"
|
|||
|
|
],
|
|||
|
|
"question": "HeavyKeeper 在桶中同时存储 fingerprint(指纹)和 count(计数),fingerprint 的核心作用是什么?",
|
|||
|
|
"options": {
|
|||
|
|
"A": "加速桶的哈希定位过程",
|
|||
|
|
"B": "作为轻量级身份标识,用于验证桶中存储的是否是当前 Key,避免全 Key 比较",
|
|||
|
|
"C": "替代 Bloom Filter 做存在性判定",
|
|||
|
|
"D": "用于 Top-K 排序的优先级计算"
|
|||
|
|
},
|
|||
|
|
"answer": "B",
|
|||
|
|
"explanation": "如果桶中只存计数而不标识属于哪个 Key,则无法区分当前桶是属于当前 Key 还是冲突的其他 Key。存储完整 Key 字符串又太耗内存。fingerprint 是 Key 的哈希值(如 64 位),作为轻量级身份标识,在 add() 和查询时用于快速判断桶是否属于当前 Key。虽然存在指纹碰撞可能,但概率极低。",
|
|||
|
|
"source": null,
|
|||
|
|
"related": []
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "sc-008",
|
|||
|
|
"type": "single_choice",
|
|||
|
|
"difficulty": 4,
|
|||
|
|
"tags": [
|
|||
|
|
"heavykeeper-topk",
|
|||
|
|
"heavykeeper"
|
|||
|
|
],
|
|||
|
|
"question": "HeavyKeeper 中 minCount 参数(例如 minCount=10)的作用是什么?",
|
|||
|
|
"options": {
|
|||
|
|
"A": "桶计数低于此值时触发 fading 衰减",
|
|||
|
|
"B": "只有累计频率超过此阈值的 Key 才有资格进入 Top-K 最小堆",
|
|||
|
|
"C": "设置每个桶的计数上限",
|
|||
|
|
"D": "控制哈希函数的最小散列粒度"
|
|||
|
|
},
|
|||
|
|
"answer": "B",
|
|||
|
|
"explanation": "minCount 是一个入门门槛:当某个 Key 的估计频率达到 minCount 时,它才有资格被插入到维护 Top-K 的最小堆(minHeap)中。这一设计避免了将大量低频 Key 放入堆中,节省内存和排序开销。只有真正可能成为热点的 Key 才会参与 Top-K 竞争。",
|
|||
|
|
"source": null,
|
|||
|
|
"related": []
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|