想象你走进一座巨大的图书馆,这里藏书千万册,但每本书都没有书名页,只有一堆堆散乱的手稿。管理员老王想了个办法:他做了一张巨大的“主题指引卡”——每张卡片正面写上主题词(比如“人工智能”),背面按顺序贴满一张张小纸条,每张纸条上写着“这本书在第3排第2架”“那本书在第5排第1架”。这样,读者想找“人工智能”相关的书,老王只要抽出对应的卡片,按纸条上的位置去取书就行。 这张“主题指引卡”就是信息检索领域最核心的发明——倒排索引(Inverted Index)。而老王这套系统的精妙之处,恰恰在于它由三个高度协作的组件构成:用来快速定位卡片位置的“抽屉序号”(term词典)、卡片背面那张“存放位置清单”(postings倒排表)、以及老王按照读者需求组合多张卡片的“查询处理”流程。今天,我们就用老王图书馆的故事,把倒排索引的三大组件一个个拆开,看懂它们是如何配合,让搜索引擎能在零点几秒内从几十亿网页中找到你想要的答案。
掌握倒排索引的组件结构与查询流程,是理解搜索引擎、数据库全文检索和所有检索引擎的基石。无论你是做推荐系统、对话式AI还是搜索开发,这份认知决定了你优化系统性能的深度。
可视化演示
展示ARP协议通过广播请求获取目标MAC地址的完整流程:缓存查询、ARP广播、交换机泛洪、单播响应、缓存更新五个阶段。
本节配有交互动效(进入视口后自动加载,约 3KB)。
阶段1:ARP缓存查询 主机A查找本地ARP缓存表,检查是否已有目标IP的MAC地址记录 缓存未命中,触发ARP地址解析流程 ARP Cache: IP→MAC ARP缓存命中率直接影响网络通信延迟
索引全景结构
老王的图书馆越开越大,他意识到“主题指引卡”不是一张,而是一套完整的系统。这套系统分为两大阶段:建立卡片库(索引构建阶段)和使用卡片库(查询处理阶段)。构建阶段像在整理图书——采购员收集新书(文档采集),老王浏览每本书的内容提取主题词(词项抽取),最后把所有卡片按主题词的字顺排列并编上架位号(排序合并)。查询阶段则像读者来找书——先根据主题词在词典抽屉里精确找到对应卡片(词项定位),然后取出卡片上的所有位置清单进行比对(倒排合并)。
技术上说,倒排索引以“词项”为键来组织文档,这与正向索引以文档为键完全不同。理解这个全景需要把握两个视角:从文档到词项的映射(构建过程),从词项到文档的反向映射(查询过程)。
老王把图书馆的卡片系统画成一张图:每个主题词就是一个抽屉(term词典),抽屉里是一串纸条(倒排表),纸条上写着书籍的架位号(文档ID)。读者来借书时,先找抽屉,再取纸条,最后按架位号找书——这就是一次完整的检索流程。整个倒排索引就是这张“抽屉-纸条”网络。
- 索引主体$\text{Index}$ 表示整个倒排索引,是检索系统的核心数据结构。
- 遍历词项集合$t \in \text{Terms}$ 表示遍历词典中的所有词项,每个词项都是一个独立单元。
- 映射关系$t \rightarrow \text{PostingsList}(t)$ 表示词项 $t$ 指向它的倒排表,这张表记录了所有包含 $t$ 的文档ID。
term词典原理
老王图书馆的抽屉越来越多,他开始面临新问题:读者说“人工智能”,他得在几千个抽屉里迅速找到对应那个。这就是 term 词典(Dictionary)的核心职责——存储所有出现过的词项,并能快速定位到该词项对应的倒排表位置。
词典设计的第一种方案是哈希表:老王给每个主题词算一个数字代号(哈希值),然后按代号把卡片放进 1000 个小格子。查找时,先算出词项的代号,直接跳到对应格子上——平均只需一次操作就找到卡片(O(1) 查询)。但代价是:哈希表要预留足够空格子避免冲突,空间浪费大(内存代价高),而且无法按字顺遍历所有词项。
第二种方案是 B 树:这像一个分层的目录树。老王把主题词按字母顺序排好,树根节点记录“中间词”来分岔,每个叶子节点存一批相邻词项。查找时从根开始逐层比较,每次读入一个节点(磁盘友好的页大小),比哈希表多几次比较但更省空间,还能支持范围查询(比如列出所有“人”开头的词项)。
老王最后把两种方案都试了一遍:小图书馆用哈希表,抽屉编号查得快;大图书馆改用 B 树,虽然找起来多几步,但能在有限房间内存下更多卡片。如果某天读者需要按主题词浏览整个目录(比如看“算法”系列),B 树还能顺着叶子节点一路扫过去——这是哈希表做不到的。
- 两种方案对照$\text{HashTable}$ 与 $\text{BTree}$ 构成词典实现的两种基本选择。
- 哈希查找代价$\text{lookup} = O(1)$ 表示哈希表可在常数时间内找到词项,$\alpha$ 是装载因子。
- B树查找代价$\text{lookup} = O(\log n)$ 表示 B 树的查找需要与节点层数成正比,$\beta$ 是页面的压缩率。
postings倒排表
抽屉找到以后,接下来看抽屉里的“纸条”——倒排表(Postings)。这是倒排索引的本体,记录了所有包含该词项的文档ID列表。老王有个重要习惯:每张纸条上的架位号按从小到大顺序排列。这看似简单的要求,却是后续高效查询的根本前提——就好像做算数时数字必须对齐,排序后的ID才能支持快速的指针归并。
倒排表还有两种典型的组织策略。链式指针是一种直观的方案:每个节点存一个文档ID和指向下一个节点的指针,像一串手拉手的小朋友。缺点是内存碎片化,而且只能从头到尾一个个走。跳表优化则是在链式结构上增加“跳跃指针”(skip pointers)——每个节点同时记录“下一个相同层级的指针”,让查找可以大步跳过不匹配的节点。老王的图书馆里,跳表就像在书架之间架起了“快速通道”,从第1排直接跳到第8排,不必逐个经过中间的书架。
更进一步,倒排表还记录词项在文档中的位置信息(position)。这就像老王在纸条上不只写“第3排”,还写“第3排第2架左起第5本”。有了位置,搜索引擎就能支持“人工智能 芯片”这样的短语查询——要求两个词不仅出现在同一文档,还必须紧挨着出现;也能在做结果页时高亮显示网页中命中的关键词。
老王后来感慨,排序这个习惯是他整个系统最聪明的决策。正因为每张纸条的架位号有序,他才能用两根手指头同时划过两张纸条,快速圈出同时出现在两处的书籍——如果纸条乱序,他就得一张张全看一遍,效率天差地别。倒排表的有序性,决定了倒排索引能在毫秒级完成复合查询。
- 倒排表本体$\text{Postings}(t)$ 表示词项 $t$ 对应的倒排表。
- 列表内容$\text{docID}_1, \text{docID}_2, \ldots$ 表示文档ID按序排列。
- 有序约束$\text{docID}_i < \text{docID}_{i+1}$ 强调ID必须严格递增,这是所有合并算法的前提。
查询处理流程
现在到了真正“查询处理”的环节。读者来找老王,想找同时包含“人工智能”和“芯片”的书。老王抽出两张卡片,把两张纸条并排放在桌上,开始合并。这个动作在技术上的术语叫AND合并(交集运算)。
AND合并的算法非常巧妙,用的是双指针推进:老王左手点着“人工智能”纸条的第一条(文档3),右手点着“芯片”纸条的第一条(文档1)。两者不同,于是指向较小文档ID(1)的那只手先挪——因为两串数字都是升序,文档1不可能出现在另一张卡片上,跳过省时间;当两手都指向文档3时,就找到了一个交集,记录下来,然后两手一起右移。如此往复,直到任一条纸走完。这个算法的时间复杂度是 $O(n+m)$,n 和 m 分别是两条倒排表的长度。
OR合并(并集运算)要简单些:老王把两张纸条合成一摞,相同的只记一次(去重),不同的都保留,最后按架位号重新排一遍。NOT 运算则像是“差集”:从第一张纸条中划掉所有出现在第二张纸条中的文档。短语查询更精细——老王的纸条记录的不只是排号,还包括具体位置,需要验证“人工智能”和“芯片”是否在同一个文档里连续出现:先按 AND 初步找出文档,再检查各自的位置列表是否有满足“间隔为1”的位置对。
老王向读者演示:他每次比较完两个不同数字,就果断放弃较小的那个,因为知道它绝不可能出现在另一张卡片上。这种“果断抛弃”的策略,让合并过程从不回头,总是直直地前进。就像两列按身高排好的队伍要找出相同身高的人,最高的那位裁判只需不断让矮个子那队前进——若两队人都已经排好序,答案很快就能浮出水面。
- AND 合并结果$\text{AND}(P_1, P_2)$ 表示求两个倒排表的交集。
- 交集条件$d \in P_1 \wedge d \in P_2$ 表示文档 $d$ 必须同时出现在两条倒排表中。
- 线性复杂度$O(n+m)$ 表示合并只需线性扫描两表各一遍。
索引构建更新
图书馆的藏书每天都在增加,老王的卡片系统需要持续维护。索引构建有两条路:静态构建与动态增量。
静态构建适用于文档集合基本不再变动时,比如建立历史档案。老王把所有书重新扫描一遍,抽取每个人的主题词,全部排序后写入磁盘。这是一次性的大工程,但完成后查询性能最优。动态增量则适用于持续更新的场景,比如新闻网站每秒钟都有新文章。
动态增量的一种经典架构是内存缓冲 + 分段合并:老王把今天新到图书的主题词先记在一个小本子上(内存临时索引),本子记满后,把整本内容追加写到一个新磁盘文件(新分段)。新分段一旦生成就不可变,以后所有新文档都写到更新的分段里。查询时分段越来越多,老王先查小本子和各个新分段,再合并结果。当分段数量积累到一定阈值,就在后台定期把几个小分段合成一个大分段——这个过程叫合并策略(Merge Policy)。
合并策略有两种取向。原地合并:直接读取旧的大分段和新的小分段,在原来文件基础上重写一个大文件,旧文件被覆盖。新段+LSM型(Log-Structured Merge):不修改旧分段,而是新写一个更大的分段,让查询去访问多个分段。LSM型更简单安全,但如果合并不及时,分段会越来越多,拖慢查询,因此需要一个像“水位线”一样的阈值机制控制合并时机。
老王的图书馆管理手册上写着几条经验:全量重新构建太慢,只适合深夜做;增量写入虽然快,但会产生碎片;合并分段要控制频率,太勤则磁盘IO压力大,太懒则查询会变慢。他最后采取“周末大扫除”策略——平时只把新卡片收进临时抽屉,到了周末深夜没人借书时才把所有临时抽屉合并进大目录。这正是 Lucene 等真实检索引擎中定期合并分段的工程缩影。
- 新分段生成$\text{NewSegment}$ 表示最终落盘的新索引分段。
- 排序操作$\text{Sort}$ 表示在内存中先按词项+文档ID排好序,保证分段内部有序。
- 触发条件$\lvert \text{InMemoryBuf} \rvert \geq \theta$ 表示内存缓冲达到阈值后触发落盘。
压缩与性能
当藏书量从一万册涨到一亿册,老王发现最占空间的既不是书架,也不是卡片本身,而是卡片上那一串串文档ID。他想了个省纸的办法——差值编码:不写完整的架位号,只记录与上一条的差距。比如“3, 7, 12“写成”3, 4, 5”——原来的数字可能高达几百万,但差值只在几十几百的范围内。小数字可以用更少的字节存储,这就是差值编码省空间的原理。
压缩不光省钱,有时还能加速。可变字节编码(VarByte)是一种典型方案:每个整数用1到4个字节存储,每个字节的最高位设为“继续标记”——如果最高位是1,说明后面还有字节;如果为0,说明这是该数的最后一个字节。老王的小本子上,"12345"这个架位号实际占2个字节而不是4个,意味着磁盘上要读的数据更少,CPU 等待IO的时间随之减少,查询自然变快。
老王算了笔账:有了差值编码和可变字节编码,原来需要10页纸的卡片目录现在只用3页。更重要的是,读者查询时他需要翻开的页数也少了,平均每次取卡片的时间从5秒降到2秒。当然,编码不是免费的:每次读取都要解码,CPU 多承担一点计算。但现代 CPU 处理简单解码的速度远快于磁盘IO等待,这笔交易几乎总是划算的——老王的经验正对应着倒排索引压缩在工程中的实际收益。
- 编码分档$\text{VarByte}(n)$ 表示对整数 $n$ 的编码结果。
- 边界条件$0 \le n \lt 2^7$ 表示小于128的数只需1字节,$2^7 \le n \lt 2^{14}$ 表示小于16384的数用2字节。
- 连续字节$b_1 b_2 b_3$ 表示每个字节最高位作“是否延续”的标记,最后字节的该位置0。
AI 应用场景
搜索引擎查询优化
阶段:检索增强生成(RAG)中的检索器构建
解决的问题:在RAG系统中,需要从海量知识库中快速定位与用户问题相关的文档片段。传统向量检索虽然能捕捉语义,但冷启动成本高且难以处理精确关键词匹配(如产品型号、法律条款)。倒排索引提供一种确定性、可解释的检索通道,与向量检索形成互补。
具体案例:某企业智能客服系统接入RAG后,用户询问“2024版服务协议中关于数据隐私的条款”。系统先用SSD固态盘上的倒排索引精确匹配“2024版”“服务协议”“数据隐私”等词项,锁定候选文档集合(通常几百篇),再对候选集做向量相似度排序,最终回答准确率从67%提升到92%。倒排索引负责“初筛”,向量检索负责“精排”,这种混合检索架构正在成为RAG系统的主流配置。
电商搜索的类目过滤
阶段:查询理解与结果粗排
解决的问题:电商平台用户常输入“iPhone 15 蓝色 256G”,期望系统能同时满足品牌+型号+颜色+内存四个维度。仅依赖向量检索容易混淆“蓝色”和“蓝色手机壳”等干扰项。倒排索引可以对商品属性建立多字段索引,用布尔查询快速过滤无关商品。
具体案例:某中等规模跨境电商(SKU数约500万)使用 Elasticsearch 的倒排索引,为品牌、颜色、内存、价格区间等字段各建一个索引字段。查询“iPhone 15 蓝色 256G”时,先分别对每个字段的倒排表做 AND 合并,4次线性扫描就能从500万商品中筛出127个完全匹配的商品,耗时不到20ms。相比之前用关系型数据库逐字段匹配耗时350ms,性能提升近18倍。
Python 演示
import mathfrom collections import defaultdict class InvertedIndex: def __init__(self): # 词典: 词项 -> {文档ID: 词频} self.dictionary = defaultdict(lambda: defaultdict(int)) # 文档ID -> 位置信息 self.doc_len = defaultdict(int) # 文档总数 self.doc_count = 0 def add_document(self, doc_id, text): # 简化分词: 按空格切分并转小写 tokens = text.lower().split() self.doc_len[doc_id] = len(tokens) for pos, token in enumerate(tokens): self.dictionary[token][doc_id] += 1 self.doc_count += 1 def postings_list(self, term): # 返回有序文档ID列表 if term not in self.dictionary: return [] return sorted(self.dictionary[term].keys()) def tf_idf(self, term, doc_id): # 计算term在doc中的TF-IDF权重 tf = self.dictionary[term][doc_id] df = len(self.dictionary[term]) idf = math.log((self.doc_count + 1) / (df + 1)) + 1 return (1 + math.log(tf)) * idf def and_query(self, terms): # AND合并: 双指针扫描两个有序倒排表 result = self.postings_list(terms[0]) for term in terms[1:]: other = self.postings_list(term) merged = [] i = j = 0 while i < len(result) and j < len(other): if result[i] == other[j]: merged.append(result[i]) i += 1 j += 1 elif result[i] < other[j]: i += 1 else: j += 1 result = merged if not result: break return result def rank_results(self, docs, terms): # 按TF-IDF加权得分排序 scores = [] for doc_id in docs: score = sum(self.tf_idf(t, doc_id) for t in terms if t in self.dictionary) scores.append((doc_id, round(score, 4))) scores.sort(key=lambda x: x[1], reverse=True) return scores if __name__ == "__main__": idx = InvertedIndex() idx.add_document(1, "cat sits on the mat") idx.add_document(2, "dog runs in the park") idx.add_document(3, "cat and dog are friends") idx.add_document(4, "bird flies over the tree") print("词典结构:", {k: dict(v) for k, v in idx.dictionary.items()}) print("cat的倒排表:", idx.postings_list("cat")) print("AND查询(cat, dog):", idx.and_query(["cat", "dog"])) print("TF-IDF排序:", idx.rank_results([1, 3], ["cat", "dog"]))
self.dictionary = defaultdict(lambda: defaultdict(int))词典采用两层嵌套字典,外层词项、内层文档ID到词频的映射。self.dictionary[token][doc_id] += 1统计词频,正对应倒排表记录词频的设计。sorted(self.dictionary[term].keys())返回有序文档ID列表,有序性是后续合并的前提。idf = math.log((self.doc_count + 1) / (df + 1)) + 1用对数+拉普拉斯平滑计算IDF权重。while i < len(result) and j < len(other):双指针扫描两个有序倒排表。elif result[i] < other[j]: i += 1较小文档ID果断前进,这是AND合并的加速关键。score = sum(self.tf_idf(t, doc_id) ...)多词查询将各词TF-IDF加权求和,体现查询处理与权重计算的结合。
常见误区
误区我以为倒排索引就是一张很大的链表,词项按字母序挂成一串,查找时每次都得从第一个词开始一个个比较,直到找到目标词项。
正解倒排索引在逻辑上分成三个独立组件,其中 term 词典负责快速定位词项,通常采用哈希表或 B 树存储,根本不需要遍历全部词项。查找“猫”这个词时,用哈希函数直接算出它在存储中的位置,平均只需一次访问;用 B 树也只需沿着树根到叶子走 O(log n) 次节点比较,而不是 O(n) 次。倒排表本身则按文档ID有序存储,而不是按词项顺序串成链表。将词典与倒排表分开,正是为了让“找到词”和“取列表”两个操作都能独立优化。
为何常见:教材上画倒排索引时常用“词项A → 文档列表1 → 词项B → 文档列表2”的连环示意图,让人误以为所有词项和列表连成了一条大链表,忘记了词典是随机访问式的跳转结构。
误区索引压缩主要是为了省钱、省存储空间,如果硬盘足够大,完全可以不做压缩,速度反而更快。
正解压缩的核心收益往往是减少磁盘IO——从磁盘读入的数据量变小,内存能缓存更多倒排表,查询延迟显著下降。差值编码把大数变成小数,可变字节编码又用更少字节存小数,磁盘读入量常能减少 50%—80%。CPU 解码的额外开销通常远小于 IO 的节省,所以压缩后查询反而更快。这就好比快递员要把 100 个包裹从仓库送到车上,如果把每个包裹的体积压缩一半,他能少跑很多趟,卸货时多花两秒拆包装完全值得。
为何常见:直觉上“解压需要算力”让人觉得压缩只会拖慢速度,忽略了现代 CPU 的解码速度远快于磁盘/网络IO延迟这一关键事实。
误区倒排表里的文档ID只要不重复就行,按什么顺序存都无所谓,反正合并时都能扫一遍。
正解有序性是高效合并算法的绝对前提。如果倒排表中文档ID乱序,双指针算法将完全失效——你无法通过“当前数字较小就跳过”来规避无意义的比较,最坏情况需要把两条列表做笛卡尔积级别的两两比较。文档ID在分配时通常按文档创建时间递增赋号,这样自然保证有序性。倒排表的设计原则高度依赖这个顺序:跳表指针、差值编码、区间查询全部建立在有序序列之上。
为何常见:初学者容易从哈希表的无序特性联想过来,认为倒排表也是一个用于等值匹配的集合,忽略了倒排表的核心应用场景是区间归并而非单点查找。
面试题
Q1. 倒排索引中 term 词典的主要作用是什么?
- A.存储文档的完整内容
- B.快速定位词项对应的倒排表← 正确答案
- C.对搜索结果进行排序
- D.对文档进行分词处理
答案:B。词典是“抽屉标签”的角色,它负责将词项映射到倒排表的位置。A错——文档内容存在正排文件而非词典中;C错——排序是查询处理阶段的多文档排序组件;D错——分词是文档预处理阶段的事。B才是词典在检索链路中的核心定位。
Q2. 关于哈希表与 B 树实现词典的区别,说法正确的是?
- A.哈希表支持范围查询
- B.B 树查找时间复杂度为 O(1)
- C.哈希表查找平均 O(1) 但内存开销更大← 正确答案
- D.B 树不可能存储在海量磁盘上
答案:C。哈希表用空间换时间,查找快但不支持前缀/范围遍历且浪费空间;B 树按页组织节点适合磁盘,支持区间扫描。A、B、D 均与事实相反,故选 C。
Q3. 倒排表中文档ID按升序排列的主要价值是什么?
- A.节省存储空间
- B.支持双指针线性合并算法← 正确答案
- C.方便做文档去重
- D.有助于词项哈希计算
答案:B。有序序列使双指针归并算法成为可能——当两条列表有序时,可以在 O(n+m) 时间内求出交集并集,不需要两两比较。A说“节省空间”不够准确,压缩才省空间,有序是压缩的前提但本身不直接缩体积。C、D 与有序性无直接关系。
Q4. AND 合并中当两个指针指向的 docID 不同且 p1 < p2 时,正确做法是?
- A.p2 指针右移一位
- B.记录 p1 后移动 p1
- C.p1 指针右移一位← 正确答案
- D.两个指针同时右移
答案:C。p1 较小,因为列表有序,p1 不可能出现在另一个列表后续元素中,直接舍弃并前进 p1。这就是“果断抛弃”策略,它在保证正确性的同时最大化效率。A会漏算 p1,B 和 D 都会产生错误交集。
Q5. 差值编码(Delta Encoding)具体存储的是?
- A.文档ID本身
- B.相邻文档ID之间的差值← 正确答案
- C.文档ID的哈希值
- D.词项在文档中出现的次数
答案:B。差值编码用相邻ID的差替代原始ID。例如 [3,7,12] 存成 [3,4,5],用更小整数大幅降低存储字节数。C 是哈希表的做法,D 是词频(tf)的统计,两者都不是差值编码的本质。
Q6. 关于可变字节编码(VarByte),以下说法正确的有?
- A.每个整数占用固定4字节
- B.字节最高位用于标记是否有后续字节← 正确答案
- C.146 在编码时需要2个字节
- D.编码后读取时必须解码才能得到原值← 正确答案
答案:BD。VarByte 每个字节的最高位为延续标记(B对),146 大于 2^7 小于 2^14 所以需要2字节(C对)。A 说固定4字节恰恰相反——VarByte 的精髓就是可变长;D 虽属实但表述不够准确,更关键是“编码后读取时必须解码”确实是使用前提,但题目要求“关于编码说法正确的”,B和C 是直接特性,D 属于使用流程。综合最优答案为 B、C。
Q7. 动态增量索引中,LSM型分段合并策略的核心特点是什么?
- A.修改原有旧分段文件来写入新文档
- B.新分段文件一旦生成就不可修改← 正确答案
- C.只能进行全量重建,无法增量
- D.合并时直接覆盖原文件路径
答案:B。LSM(Log-Structured Merge)型策略会不断创建新分段,旧分段只读不写,通过后台定期合并降低分段数量。A、D 描述的是原地合并策略而非 LSM;C 与增量更新的前提矛盾。LSM 的不可变设计简化了并发控制并提升了写入吞吐。
Q8. 以下属于倒排索引中查询处理常见操作的有?
- A.AND 双指针交集合并← 正确答案
- B.OR 并集归并去重← 正确答案
- C.NOT 差集求反运算← 正确答案
- D.文档全编码压缩
答案:ABC。A、B、C 都是倒排表上的经典布尔运算,分别对应交集、并集、差集。D 属于索引压缩优化,是构建阶段的处理,不属于查询处理操作。题目问“查询处理常见操作”,故只选 A、B、C。
Q9. 在索引构建中,内存缓冲达到阈值后的正确处理是?
- A.丢弃最旧的文档数据
- B.在缓冲区上原地排序然后直接查
- C.将缓冲区排序后写入新的不可变分段← 正确答案
- D.将所有旧分段合并后覆盖写入
答案:C。动态索引的标准流程是内存缓冲先积累,达到阈值后排序输出为一个新分段,该分段不可变,后续再靠后台合并策略整理。A 丢数据显然错误;B 不落盘无法持久化;D 是合并策略的具体动作但发生在更晚的时机,不是缓冲区触发后的第一步。
Q10. 关于倒排索引压缩与查询性能的关系,正确的工程认识有?
- A.压缩后查询不一定变慢,甚至可能更快← 正确答案
- B.压缩的主要收益之一是减少磁盘IO← 正确答案
- C.压缩编码不增加任何CPU开销
- D.压缩后内存能容纳更多倒排表缓存← 正确答案
答案:ABD。压缩减少磁盘读入量,IO变快,且更多倒排表可缓存在内存(D对),所以查询未必变慢(A对)。B 说明了压缩的关键收益机制。C 错误——解码一定消耗 CPU,正确的工程观点是 CPU 开销小于 IO 节省,才让“压缩提速”成立。