Apache Lucene 底层原理:从倒排索引到一次搜索的执行过程

假设你要从一千万篇文章中找出包含"Java 线程池"的内容。最直接的办法是逐篇读取正文,再判断关键词是否出现。但文章越多,读取和比较的成本就越高。

Lucene 换了一个思路:**写入文章时,先整理好"哪个词出现在哪些文章里";搜索时,直接查这份目录,这就是倒排索引。**那么它底层究竟是怎样的,如何一步一步实现呢?

一、Lucene 到底是什么?

Lucene 是一个用 Java 写的开源全文检索库 (Apache 顶级项目)。应用可以把它作为依赖引入,在进程内完成建索引、文本检索和相关性排序。它不是一个独立运行的"搜索服务器"或数据库,而是一个你嵌入到自己程序里的搜索引擎内核。提供的主要能力:

  • 分词(Analyzer):把文本切成词,处理大小写、停用词、词干提取;
  • 多种查询:关键词、短语、布尔组合、通配符、模糊匹配、范围查询;
  • 相关性打分:默认使用 BM25 算法,按词频、文档长度等因素排序
  • 高亮、排序、分面统计等辅助功能

它和 Elasticsearch、Solr 的关系:

定位
Lucene 底层库,只负责单机的索引与检索,没有网络接口、没有集群
Elasticsearch / Solr / OpenSearch 基于 Lucene 封装的搜索服务,提供 REST API、分布式、分片副本、运维管理等

二、从文章到倒排表:把查询工作提前做

假设经过分词和规范化之后,有三篇文章:

文档编号 词序列
0 java、线程池、原理
1 java、内存、原理
2 线程池、参数、配置

按文档寻找词,是我们自然理解文章的方式。倒排索引则反过来组织:

词项(Term) 文档编号列表
java 0、1
线程池 0、2
原理 0、1
内存 1
参数 2
配置 2

这个时候,如果搜索"同时包含 java 和线程池",就变成两个有序列表求交集:

java → 0, 1

线程池 → 0, 2

交集 → 0

搜索过程中无需先读三篇文章的完整正文。匹配完成之后,再读取命中文档中需要展示的字段。

三、分词决定了你实际上在搜索什么

Lucene 接收到一段文本后,会通过 Analyzer(分词器) 得到 Token 流。典型流程是:

原始字符

→ CharFilter:可选的字符预处理

→ Tokenizer:切分文本

→ TokenFilter:大小写规范化、停用词等处理

→ 用处理后的词建立索引

例如把 Java Thread Pool 处理成 java、thread、pool。中文还涉及词语边界,"线程池"是否成为一个词,取决于采用的分析器。

四、词这么多,Lucene 怎么快速找到一个词?

倒排索引实际需要解决两个问题:先找到词,再找到它对应的文档列表。在源码BlockTree的实现中,共享前缀的词被组织到块中,并用 FST 保存用于定位这些块的前缀索引。例如 cat、car共享前缀ca,不必把每个词都当成完全无关的字符串处理。FST 可以理解为一种紧凑的路径结构:沿输入字符向前走,最终获得定位信息。如下图所示:

查找入口LeafReader.postings(Term term, int flags) 的核心路径,源码如下:

java 复制代码
final Terms terms = Terms.getTerms(this, term.field());
final TermsEnum termsEnum = terms.iterator();
if (termsEnum.seekExact(term.bytes())) {
  return termsEnum.postings(null, flags);
}
return null;

这几行代码分别做了三件事:

  • 找到指定字段的词典
  • 在词典中精确定位目标词项
  • 返回该词项的倒排迭代器

五、倒排表为什么既省空间,又查得快?

假设一个词出现在这些文档中:

原始编号:100、103、104、110

相邻差值:100、3、1、6

编号递增,就可以保存差值。差值通常比完整编号小,更适合紧凑编码。Lucene 还会把整数分块编码,兼顾压缩率和批量解码效率。查询时也不必从头逐个扫完。迭代器的 advance(target) 可以前进到第一个大于等于目标编号的位置,底层利用跳跃信息减少不必要的解码。因此,两个列表求交集类似于下面伪代码:

a = A.nextDoc()

b = B.nextDoc()

while a 和 b 都未结束:

if a < b: a = A.advance(b)

else if b < a: b = B.advance(a)

else:

收集 a

a = A.nextDoc()

b = B.nextDoc()

六、写入索引:先积累,再生成 Segment

Lucene 不会每新增一篇文章,就把所有历史倒排表重新写一遍。典型写入路径可以大致概括为:

Document → 字段处理与分词 → 内存索引缓冲 → flush → 生成新的Segment

Segment,中文常称"段",可以理解为一份独立的小索引,拥有自己的词典、倒排表以及其他字段数据。一个 Lucene 索引通常包含多个段,搜索时分别查询这些段,再统一收集结果。flush、刷新 Reader、commit 有什么区别?

操作 主要解决的问题 是否意味着持久提交
flush 把缓冲中的索引数据写到 Directory 否
从 Writer 打开或刷新近实时 Reader 让新的 Reader 看到尚未提交的变化 否
commit 发布提交点并同步相关文件 是,依赖存储正确实现同步语义

七、删除与更新:旧文章为什么还占空间?

如果一篇文章出现在很多词的倒排表里,删除时逐个修改这些列表会非常昂贵。普通硬删除的处理方式,是记录文档已失效,搜索时排除它。空间通常等到后续合并时再回收。所以"搜索不到了"不代表"文件立刻变小了"。

旧文档 → 标记删除 → 后续合并清理

新文档 → 正常建立索引

八、用一个小例子简单串一下

java 复制代码
// 方法声明需 throws IOException。
try (Directory directory = new ByteBuffersDirectory();
     //分词器
     Analyzer analyzer = new StandardAnalyzer();
     //建索引
     IndexWriter writer = new IndexWriter(
         directory, new IndexWriterConfig(analyzer))) {
    //文档写入
    Document doc = new Document();
    doc.add(new StringField("id", "article-1", Field.Store.YES));
    doc.add(new TextField("body", "Java thread pool", Field.Store.YES));
    writer.addDocument(doc);

    //根据条件搜索
    try (DirectoryReader reader = DirectoryReader.open(writer)) {
        IndexSearcher searcher = new IndexSearcher(reader);
        Query query = new TermQuery(new Term("body", "java"));
        TopDocs hits = searcher.search(query, 10);

        StoredFields storedFields = searcher.storedFields();
        for (ScoreDoc hit : hits.scoreDocs) {
            Document found = storedFields.document(hit.doc);
            System.out.println(found.get("id") + ": " + found.get("body"));
        }
    }
}

整个过程大概如下:

原文:Java thread pool

│分词并建立索引

▼

词项:java、thread、pool

│TermQuery 搜索 java

▼

命中文档,获得内部编号和得分

│StoredFields 根据编号取值

▼

输出:article-1: Java thread pool

相关推荐
五网7 小时前
软件测试用例模板怎么写:从用例设计到执行记录的全套指南
软件测试·论文阅读·全文检索·测试用例·论文笔记·用例设计·用例模板
翊众建站5 天前
用 JSON-LD 给企业官网做结构化数据:LocalBusiness 与 FAQPage 实践笔记
人工智能·搜索引擎·json
fengye2071615 天前
第三章 编译cpython
大数据·elasticsearch·搜索引擎
Elastic 中国社区官方博客5 天前
AgentHack Shanghai|基于阿里云AI搜索 Elasticsearch x Qoder 构建AI Agent应用
大数据·人工智能·elasticsearch·搜索引擎·阿里云·云计算·全文检索
真上帝的左手5 天前
30. 技术专题-‌应用框架组件-搜索引擎-ES
大数据·分布式·elasticsearch·搜索引擎·中间件
极限实验室6 天前
Easysearch 布尔查询子句重排序(二)|ConjunctionDISI 按 cost 排序源码解析
后端·搜索引擎·性能优化
Elastic 中国社区官方博客6 天前
基于 Elasticsearch 与 Jina AI 的全模态智能搜索实践:技术全解析
大数据·人工智能·elasticsearch·搜索引擎·ai·信息可视化·jina
Sayai6 天前
Elasticsearch ingest pipeline 被禁用之后:性能代价、安全治理与数据入库时间的四种替代方案
大数据·运维·安全·elasticsearch·搜索引擎·性能优化
LaughingZhu6 天前
Product Hunt 热榜 2026-11-02:Screen Studio 4.0、ClawsGO Science、Riverside Agents
人工智能·深度学习·神经网络·数码相机·搜索引擎·百度
LaughingZhu7 天前
Product Hunt 热榜 2026-11-01:Specsight、SKI、telemetry.dev
人工智能·深度学习·神经网络·搜索引擎·百度