feat: add ThumbUP 项目 — 70 questions across 5 subtopics (cache-protection, two-level-cache, lua-bucketing-sync, heavykeeper-topk, distributed-lock)
Deploy Examination / deploy (push) Successful in 20s

This commit is contained in:
2026-09-09 21:59:46 +08:00
parent 3447dbd4ee
commit 85710ded73
21 changed files with 2222 additions and 0 deletions
@@ -0,0 +1,176 @@
{
"topic": "heavykeeper-topk",
"type": "code_reading",
"schema_version": "1.0.0",
"generated": "2026-09-09T21:42:00+08:00",
"questions": [
{
"id": "cr-001",
"type": "code_reading",
"difficulty": 3,
"tags": [
"heavykeeper-topk",
"heavykeeper"
],
"question": "阅读以下 HeavyKeeper.add() 方法的核心逻辑,回答子问题。",
"code": "public AddResult add(String key, int increment) {\n byte[] keyBytes = key.getBytes();\n long itemFingerprint = hash(keyBytes);\n int maxCount = 0;\n for (int i = 0; i < depth; i++) {\n int bucketNumber = Math.abs(hash(keyBytes)) % width;\n Bucket bucket = buckets[i][bucketNumber];\n synchronized (bucket) {\n if (bucket.count == 0) {\n bucket.fingerprint = itemFingerprint;\n bucket.count = increment;\n } else if (bucket.fingerprint == itemFingerprint) {\n bucket.count += increment;\n } else {\n for (int j = 0; j < increment; j++) {\n double decay = bucket.count < LOOKUP_TABLE_SIZE ?\n lookupTable[bucket.count] :\n lookupTable[LOOKUP_TABLE_SIZE - 1];\n if (random.nextDouble() < decay) {\n bucket.count--;\n if (bucket.count == 0) {\n bucket.fingerprint = itemFingerprint;\n bucket.count = increment - j;\n break;\n }\n }\n }\n }\n }\n maxCount = Math.max(maxCount, bucket.count);\n }\n total += increment;\n return new AddResult(maxCount, ...);\n}",
"language": "java",
"explanation": "这段代码展示了 HeavyKeeper 的核心插入逻辑。对于每个 Key,在 depth 层中分别定位桶,然后根据桶的状态(空/匹配/冲突)执行不同操作。冲突时通过概率衰减尝试腾空桶。",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "single_choice",
"question": "当 `bucket.fingerprint == itemFingerprint` 为真时,说明什么?",
"options": {
"A": "发生了哈希冲突,需要进行衰减淘汰",
"B": "该桶当前存储的正是当前 Key,直接累加计数",
"C": "桶已被清空,可以写入新指纹",
"D": "需要将该 Key 移到下一层的桶中"
},
"answer": "B",
"explanation": "bucket.fingerprint == itemFingerprint 表示桶中已存储的指纹与当前 Key 的指纹一致,说明该桶属于当前 Key,此时直接将计数累加 increment,无需任何衰减操作。这是最理想的分支——热 Key 被再次命中。"
},
{
"index": 2,
"type": "single_choice",
"question": "在冲突衰减分支中,decay 值的计算方式为 `lookupTable[bucket.count]`(当 count < 256 时),这意味着桶计数越高,衰减概率如何变化?",
"options": {
"A": "衰减概率越大,更容易被淘汰",
"B": "衰减概率越小,越难被淘汰",
"C": "衰减概率保持不变",
"D": "衰减概率先增后减"
},
"answer": "B",
"explanation": "lookupTable[i] = 0.92^i,这是一个单调递减函数。bucket.count 越大,lookupTable[bucket.count] 越小,意味着递减概率越低。因此高频热 Key 的桶计数大、衰减概率小,很难被冷 Key 冲掉;而冷 Key 的桶计数小、衰减概率大,容易被清零淘汰。"
},
{
"index": 3,
"type": "single_choice",
"question": "以下哪个场景会触发 `bucket.count = increment - j` 这行代码?",
"options": {
"A": "桶为空时直接写入新 Key",
"B": "当前 Key 的指纹与桶指纹匹配时累加计数",
"C": "冲突衰减过程中桶计数被减到 0,桶被腾空",
"D": "minHeap 中的最小元素被淘汰时"
},
"answer": "C",
"explanation": "bucket.count == 0 发生在冲突衰减循环中:经过 j 次成功的衰减递减后,桶计数从原来的状态被减到了 0。此时桶已腾空,写入当前 Key 的指纹,并将 count 设为 increment - j(因为已经衰减了 j 次,还剩 increment - j 次未使用)。break 跳出衰减循环。"
}
]
},
{
"id": "cr-002",
"type": "code_reading",
"difficulty": 4,
"tags": [
"heavykeeper-topk",
"heavykeeper"
],
"question": "阅读以下 HeavyKeeper 的 add() 方法完整片段(含 minHeap 管理),回答子问题。",
"code": "// ... 前面的桶操作逻辑 ...\n// 经过 depth 层桶操作后,得到 maxCount\n\ntotal += increment;\n\n// === minHeap 管理 ===\nsynchronized (minHeap) {\n if (maxCount >= minCount) {\n // 检查 Key 是否已在堆中\n Node existing = heapIndex.get(key);\n if (existing != null) {\n existing.count = maxCount;\n minHeap.remove(existing);\n minHeap.add(existing);\n } else {\n if (minHeap.size() < k) {\n Node node = new Node(key, maxCount);\n minHeap.add(node);\n heapIndex.put(key, node);\n } else {\n Node min = minHeap.peek();\n if (maxCount > min.count) {\n heapIndex.remove(min.key);\n minHeap.poll();\n Node node = new Node(key, maxCount);\n minHeap.add(node);\n heapIndex.put(key, node);\n }\n }\n }\n }\n}",
"language": "java",
"explanation": "这段代码展示了 minHeap 的完整管理逻辑:先检查 maxCount 是否超过 minCount 门槛,再判断 Key 是否已在堆中,最后根据堆大小决定是直接插入还是替换堆顶最小元素。",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "single_choice",
"question": "为什么要检查 `maxCount >= minCount` 才能进入 minHeap 操作?",
"options": {
"A": "防止整数溢出",
"B": "过滤低频 Key,避免大量低频元素占用堆空间",
"C": "确保 fingerprint 已初始化",
"D": "保证哈希分布均匀"
},
"answer": "B",
"explanation": "minCount 是一个入门门槛。数据流中绝大多数 Key 是低频的,如果每个 Key 都加入堆,堆的大小会无限膨胀。只有频率达到 minCount 的 Key 才值得参与 Top-K 竞争,这大大减少了堆的操作次数和内存占用。"
},
{
"index": 2,
"type": "single_choice",
"question": "当 minHeap.size() == k 且 maxCount > min.count 时,代码执行了什么操作?",
"options": {
"A": "在堆中插入新节点,堆大小变为 k+1",
"B": "移除堆顶(最小元素),将新 Key 插入堆中,堆大小保持为 k",
"C": "忽略新 Key,不做任何操作",
"D": "将堆顶元素的 count 更新为 maxCount"
},
"answer": "B",
"explanation": "当堆已满(size == k)且新 Key 的频率大于堆顶最小元素时,先 poll() 移除堆顶,再 add() 插入新 Key。这样堆大小保持为 k,同时将更高频的 Key 纳入 Top-K。这是维护最小堆实现 Top-K 的标准操作。"
},
{
"index": 3,
"type": "single_choice",
"question": "heapIndex(Map<String, Node>)的作用是什么?",
"options": {
"A": "存储所有 Key 的指纹",
"B": "记录 Key 到堆节点的映射,支持快速查找和更新已在堆中的 Key",
"C": "记录每层桶的访问频率",
"D": "缓存 fading() 操作的中间结果"
},
"answer": "B",
"explanation": "Java 的 PriorityQueue 不支持高效的随机查找和删除。heapIndex 维护了 Key → Node 的映射,当一个已在堆中的 Key 频率更新时,可以通过 heapIndex 快速找到对应 Node,执行 remove + add 更新堆。如果不维护这个索引,每次更新都需要 O(n) 遍历堆。"
}
]
},
{
"id": "cr-003",
"type": "code_reading",
"difficulty": 5,
"tags": [
"heavykeeper-topk",
"heavykeeper"
],
"question": "阅读以下 HeavyKeeper.fading() 方法的完整实现,回答子问题。",
"code": "public void fading() {\n // 第一阶段:对所有桶执行右移减半\n for (Bucket[] row : buckets) {\n for (Bucket bucket : row) {\n synchronized (bucket) {\n bucket.count = bucket.count >> 1;\n }\n }\n }\n // 第二阶段:同步衰减 minHeap 中所有节点\n synchronized (minHeap) {\n PriorityQueue<Node> newHeap = new PriorityQueue<>(\n k, Comparator.comparingLong(n -> n.count)\n );\n for (Node node : minHeap) {\n newHeap.add(new Node(node.key, node.count >> 1));\n }\n minHeap.clear();\n minHeap.addAll(newHeap);\n }\n // 第三阶段:全局计数减半\n total = total >> 1;\n}",
"language": "java",
"explanation": "fading() 实现了全局指数衰减,通过三阶段操作:桶计数减半、堆节点计数减半、全局 total 减半。这使 HeavyKeeper 能适应数据流分布的时间变化。",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "single_choice",
"question": "fading() 为什么要新建一个 newHeap 而不是直接修改 minHeap 中的 Node.count?",
"options": {
"A": "因为 Node.count 是 final 字段,不可修改",
"B": "因为直接修改 Node 的 count 会破坏 PriorityQueue 的堆序性质,导致堆结构失效",
"C": "为了提高内存分配效率",
"D": "因为 minHeap 不支持迭代器"
},
"answer": "B",
"explanation": "PriorityQueue 是基于堆序(每个父节点 ≤ 子节点)维护的。如果直接修改 Node 的 count 值,堆中元素的顺序关系会被破坏,后续的 peek/poll 操作会返回错误结果。正确做法是创建新堆,将衰减后的节点逐个插入,重新建立堆序。"
},
{
"index": 2,
"type": "single_choice",
"question": "如果某个 Key 的桶 count 在 fading() 前为 3,执行 fading() 后变为多少?",
"options": {
"A": "3",
"B": "1",
"C": "0",
"D": "1.5"
},
"answer": "B",
"explanation": "3 >> 1 = 1(二进制 11 右移一位变成 1,即整数除以 2 向下取整)。fading() 后该桶的 count 从 3 变为 1。如果原来是 1,则 1 >> 1 = 0,桶被清空。这种衰减让低频 Key 逐渐被淘汰。"
},
{
"index": 3,
"type": "single_choice",
"question": "fading() 执行后,一个频率为 100 的热 Key 经过多次 fading() 后频率变为约 12(≈100/8),这意味着大约执行了几次 fading()?",
"options": {
"A": "2 次",
"B": "3 次",
"C": "4 次",
"D": "5 次"
},
"answer": "B",
"explanation": "每次 fading() 将计数减半:100 → 50 → 25 → 12,经过 3 次 fading() 后频率约为 12(精确值为 12,因为 100>>1=50, 50>>1=25, 25>>1=12)。这体现了 fading 的指数衰减效果——频率每经过一次 fading() 就减半。"
}
]
}
]
}
+30
View File
@@ -0,0 +1,30 @@
{
"slug": "heavykeeper-topk",
"name": "HeavyKeeper Top-K 探测",
"description": "HeavyKeeper算法原理、指数衰减、vs CMS对比、Top-K堆管理",
"tags": [
"cms",
"heavykeeper",
"heavykeeper-topk",
"hot-key-detection"
],
"difficulty_range": [
3,
5
],
"schema_version": "1.0.0",
"updated": "2026-09-09",
"question_files": [
"single_choice",
"code_reading",
"short_answer"
],
"stats": {
"total": 14,
"by_type": {
"single_choice": 8,
"code_reading": 3,
"short_answer": 3
}
}
}
@@ -0,0 +1,76 @@
{
"topic": "heavykeeper-topk",
"type": "short_answer",
"schema_version": "1.0.0",
"generated": "2026-09-09T21:42:00+08:00",
"questions": [
{
"id": "sa-001",
"type": "short_answer",
"difficulty": 3,
"tags": [
"heavykeeper-topk",
"cms"
],
"question": "请对比说明 HeavyKeeper 和 Count-Min Sketch (CMS) 在处理「冷 Key 与热 Key 哈希冲突」时的行为差异,以及这种差异对 Top-K 探测准确性的影响。",
"answer": "CMS:冷 Key 与热 Key 哈希到同一桶时,冷 Key 的频率会累加到热 Key 上,且 CMS 只做加法不做减法,导致热 Key 的频率被持续高估(正偏差),冷 Key 的存在会'污染'热 Key 的计数。HeavyKeeper:采用概率衰减淘汰策略——当冷 Key 冲突到热 Key 的桶时,会以较高概率递减桶计数(因为冷 Key 频率低对应桶 count 小,衰减概率高);而热 Key 自身频繁命中会保持高 count,衰减概率低,不易被淘汰。结果是冷 Key 的污染被逐步清除,热 Key 的计数更准确。",
"keywords": [
"冷热冲突",
"概率衰减",
"计数累加",
"正偏差",
"污染"
],
"scoring_rubric": "评分标准:1. 准确描述 CMS 在冷热冲突时的行为——冷 Key 计数累加到热 Key 导致正偏差(2分);2. 准确描述 HeavyKeeper 的概率衰减淘汰机制——低 count 衰减概率高、高 count 衰减概率低(2分);3. 对比两者对 Top-K 准确性的影响——CMS 会误判、HeavyKeeper 更准确(1分)",
"explanation": "CMS 的核心缺陷是只增不减,冷 Key 的污染是永久性的。HeavyKeeper 通过指数衰减(0.92^count)让冷 Key(小 count)容易被淘汰,而热 Key(大 count)几乎不受影响,实现了'自清洁'效果。这是 HeavyKeeper 相比 CMS 在 Top-K 场景下的核心优势。",
"source": null,
"related": []
},
{
"id": "sa-002",
"type": "short_answer",
"difficulty": 4,
"tags": [
"heavykeeper-topk",
"heavykeeper"
],
"question": "HeavyKeeper 的 fading() 机制是如何实现时间窗口适应性的?请解释其工作原理以及为什么需要定期调用。",
"answer": "fading() 通过全局衰减实现时间窗口适应:每次调用时,将所有桶的计数、minHeap 中节点的计数、以及全局 total 都执行右移一位(除以 2)。效果等价于指数衰减的时间窗口——历史数据的权重随时间指数衰减。定期调用 fading() 的必要性:1. 数据流分布会随时间变化(如电商大促导致热点切换),不衰减会导致旧热点长期占据 Top-K 位置;2. 衰减让低频的旧 Key 逐渐被淘汰(count 趋向 0),腾出位置给新的热点 Key;3. 模拟滑动时间窗口,使算法只关注近期的数据分布。",
"keywords": [
"fading",
"指数衰减",
"时间窗口",
"右移减半",
"分布变化"
],
"scoring_rubric": "评分标准:1. 正确解释 fading() 的操作——对桶计数、堆节点、全局 total 统一执行右移减半(2分);2. 说明指数衰减的时间窗口效果——历史权重随时间递减(1分);3. 解释定期调用的必要性——适应数据分布变化、淘汰旧热点、腾出空间给新热点(2分)",
"explanation": "fading() 是 HeavyKeeper 区别于 CMS 的关键机制之一。CMS 没有内置衰减,数据分布变化后只能重建。HeavyKeeper 通过简单的位运算实现了优雅的指数衰减,兼顾了实现简洁性和运行效率。",
"source": null,
"related": []
},
{
"id": "sa-003",
"type": "short_answer",
"difficulty": 5,
"tags": [
"heavykeeper-topk",
"heavykeeper",
"hot-key-detection"
],
"question": "假设需要在生产环境中使用 HeavyKeeper 实现 Top-100 热 Key 检测(width=100000, depth=5, minCount=10),请分析:(1) 该配置的内存开销大约是多少?(2) 与同等精度的 CMS 相比,HeavyKeeper 的内存开销有何差异?(3) 在什么场景下应该选择 HeavyKeeper 而非 CMS?",
"answer": "(1) 内存开销分析:每个 Bucket 存储一个 fingerprint(8 字节 long)+ 一个 count(4 字节 int)+ 对象开销,假设约 24-32 字节。总桶数 = 5 × 100000 = 500000,桶内存 ≈ 500000 × 24 ≈ 12MB。加上 lookupTable(256 × 8 = 2KB)、minHeap(100 个节点,可忽略)、其他元数据,总计约 12-15MB。(2) 与 CMS 对比:CMS 每个计数器通常用 4 字节(int),相同 width×depth 配置下 CMS 只需 5 × 100000 × 4 ≈ 2MB。HeavyKeeper 多了 fingerprint 存储和对象开销,内存约为 CMS 的 5-7 倍。但 HeavyKeeper 的 Top-K 结果更准确(冷 Key 自清洁),这是用额外内存换精度。(3) 选择 HeavyKeeper 的场景:需要实时 Top-K 热 Key 探测且数据流分布会随时间变化(如 CDN 访问热点、电商流量分析);选择 CMS 的场景:只需要频率估计且内存极度受限,或数据分布稳定不需要衰减。",
"keywords": [
"内存开销",
"width",
"depth",
"fingerprint",
"CMS 对比",
"场景选择"
],
"scoring_rubric": "评分标准:1. 正确计算 HeavyKeeper 内存开销——桶数 width×depth、每桶 fingerprint+count 约 24-32 字节、总计约 12-15MB(2分);2. 正确对比与 CMS 的内存差异——CMS 仅存计数约 2MB,HeavyKeeper 约 5-7 倍但精度更高(2分);3. 准确区分适用场景——HeavyKeeper 适合动态变化的 Top-K,CMS 适合静态频率估计或内存极度受限场景(1分)",
"explanation": "内存分析是工程实践中的关键考量。HeavyKeeper 的额外内存主要来自 fingerprint 存储和 Java 对象开销。在生产环境中,如果内存预算允许且需要高质量的 Top-K 结果,HeavyKeeper 是更好的选择。如果内存极度紧张或只需要粗粒度的频率估计,CMS 可能更合适。",
"source": null,
"related": []
}
]
}
@@ -0,0 +1,168 @@
{
"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": []
}
]
}