lucene基础知识
song

什么是 Lucene?

Apache Lucene 是一个开源的、高性能的全文检索库,用 Java 编写,广泛用于实现文本搜索功能。它不是一个完整的搜索引擎,而是一个提供索引和搜索功能的库,开发者可以基于它构建自定义的搜索应用。Lucene 被许多知名项目(如 Elasticsearch、Solr)用作底层核心技术。

索引(Index)

  • Lucene 的索引是一个逻辑单元,包含一组文档的倒排索引和其他元数据。
  • 索引分为多个不可变的段,支持增量写入和后台合并。每个段独立存储,允许多线程并发查询。
  • Lucene 的索引文件是一组二进制文件,用于持久化存储倒排索引、文档元数据、字段数据等信息。

Lucene 的索引文件按功能划分为 词典、倒排列表、存储字段、文档值、段元数据 等模块。

1. 段元数据文件

文件名 作用
segments_N 记录当前索引的所有段信息(段名、文档数、删除文档数、版本等)。核心元数据文件,每次提交(commit)生成一个新的 segments_N
write.lock 写入锁文件,防止多个进程同时修改索引。

2. 词典与倒排索引文件

文件名 作用
.tim (Terms Dictionary) 存储词项字典(Term Dictionary),使用 FST(有限状态转换机) 压缩存储所有词项,并指向对应的倒排列表。
.tip (Terms Index) 词项字典的索引文件,加速词项查找(类似 B-Tree 结构)。
.doc (Postings List) 存储倒排列表(Posting List),包含文档 ID、词频(TF)、位置(Positions)等信息。

3. 存储字段(Stored Fields)

文件名 作用
.fdt (Stored Fields Data) 存储原始文档的字段值(按文档顺序存储),例如保留的标题、正文等原始数据。
.fdx (Stored Fields Index) 存储字段的索引,记录每个文档在 .fdt 文件中的偏移量和长度,支持快速定位。

4. 文档值(DocValues)

文件名 作用
.dvd (DocValues Data) 列式存储数据文件,用于排序、聚合、脚本计算等(如数值、日期、字符串类型)。
.dvm (DocValues Metadata) 存储文档值的元数据(数据类型、压缩方式、数据偏移等)。

5. 词向量(Term Vectors)

文件名 作用
.tvx (Term Vector Index) 词向量的索引文件,记录每个文档的词向量在 .tvd 文件中的位置。
.tvd (Term Vector Data) 存储词向量数据(词项、频率、位置等),用于高亮显示或相关性分析。
.tvf (Term Vector Fields) 存储词向量字段的元信息。

6. 标准化信息(Norms)

文件名 作用
.nvd (Norms Data) 存储字段的标准化信息(如长度归一化因子),用于相关性评分(TF-IDF、BM25)。
.nvm (Norms Metadata) 标准化信息的元数据。

7. 其他辅助文件

文件名 作用
.si (Segment Info) 段的元数据文件,记录段的文档数、删除文档、版本等。
.del (Deletions) 标记段中已删除的文档(BitSet 格式)。
.liv (Live Documents) 标记段中存活的文档(BitSet 格式),用于快速过滤已删除文档。

8. 复合文件

复合文件格式(Compound File Format) 是一种将多个小型索引文件合并为更少文件的机制,目的是减少文件数量、优化存储和 I/O 性能。

扩展名 名称 作用
.cfs Compound File Data 存储合并后的所有索引数据(如倒排索引、词条字典、存储字段等)。
.cfe Compound File Entries 存储 .cfs 文件内各子文件的元数据(如子文件的起始位置、长度、名称等)。
关键特性
  1. 合并索引段
    • 每个段(Segment)默认生成一个 .cfs 和 .cfe 文件对,替代原本分散的多个小文件(如 .fdt.fdx.tim.doc 等)。
    • 示例
      • 禁用复合格式的段文件:_0.fdt_0.fdx_0.tim_0.doc
      • 启用复合格式的段文件:_0.cfs_0.cfe
  2. 优化文件管理
    • 减少文件句柄:避免操作系统对大量小文件的句柄限制(尤其在 Windows 系统上)。
    • 提升顺序读性能:合并后的大文件在机械硬盘(HDD)上顺序读取更高效。
    • 简化备份/迁移:只需操作少量文件即可处理整个索引段。
  3. 灵活配置
    • 可通过 IndexWriterConfig.setUseCompoundFile(true/false) 启用或禁用复合文件格式。
    • 启用(默认):生成 .cfs + .cfe,适合通用场景。
    • 禁用:生成多个独立文件,适合需要并发读写或 SSD 优化场景。

索引文件的结构示例

1
2
3
4
5
6
7
8
9
10
11
12
13
索引目录/
├── segments_5 # 当前活跃段的元数据(版本5)
├── write.lock # 写入锁文件
├── _0.fdt # 段0的存储字段数据
├── _0.fdx # 段0的存储字段索引
├── _0.tim # 段0的词项字典
├── _0.doc # 段0的倒排列表
├── _0.dvd # 段0的文档值数据
├── _0.dvm # 段0的文档值元数据
├── _1.fdt # 段1的存储字段数据
├── _1.fdx # 段1的存储字段索引
├── _1.tim # 段1的词项字典
└── _1.doc # 段1的倒排列表
文件后缀 用途 示例内容
.tim 词项字典(Term Dictionary) 存储词项及指向倒排列表的指针。
.tip 词项字典索引(快速定位词项位置) 类似B树的索引结构,加速词项查找。
.doc 倒排列表(Posting List) 存储文档ID、词频、位置等数据。
.fdt 存储字段的原始数据(Stored Fields) 文档1的原始内容:”hello world”。
.fdx 存储字段的索引(指向.fdt中的位置) 文档1的起始偏移量:0,长度:11字节。
.dvd 文档值数据(DocValues) 列式存储的数值或排序字段。
.dvm 文档值元数据(如数据类型、压缩方式) 记录.dvd文件的结构信息。

文档(Document)

索引和搜索的基本单元,由多个字段(Field)组成,例如一篇文章的标题、正文、作者等。

  • 文档是 Lucene 的最小数据单元,包含一组字段(Fields)。
  • 每个字段是一个键值对,键是字段名称,值是字段内容(可以是文本、数字、日期等)。
  • 文档通过唯一的文档 ID(Doc ID)标识,Doc ID 是 Lucene 内部的整数,递增分配。

字段(Field)

每个文档都由一个或多个字段组成。字段是键值对,例如 title:"Elasticsearch"body:"这是一个关于 Elasticsearch 的文档"。字段可以有不同的类型(文本、数字、日期等),并且可以配置不同的索引行为(是否被搜索、是否存储原始值、是否计算词频等)。

  • 字段是文档的组成部分,可以存储文本、数字、日期等。
  • 字段定义了文档的内容和存储方式。Lucene 支持以下字段属性:
    • Indexed:是否为字段创建倒排索引(用于搜索)。
    • Stored:是否存储字段的原始值(用于返回查询结果)。
    • Tokenized:是否对字段进行分词(生成 Term)。
    • DocValues:是否为字段生成列式存储(用于排序、聚合)。
  • Lucene 的存储是「字段为中心(field-centric)」的,所有的索引/数据组织都围绕“字段”展开。
  • Lucene 的倒排索引是 按字段组织的,每个字段有自己独立的一套倒排结构。

分析器(Analyzer)

分析器(Analyzer) 是 Lucene 中处理文本的核心组件,负责将原始文本转换为可索引的 词项(Term)。它的作用类似于“文本加工流水线”,通过 分词、过滤、标准化 等步骤,将复杂文本(如句子、段落)拆解为适合搜索的规范化词项。例如,将句子 "The quick Brown Fox!" 转换为 ["quick", "brown", "fox"]

分析器的核心组成

  1. 字符过滤器(CharFilter)
  2. 分词器(Tokenizer)
  3. 词元过滤器(TokenFilter)

字符过滤器(CharFilter)

  • 作用:预处理原始文本字符(如替换、删除、添加字符)。
  • 常见场景
    • 移除 HTML 标签(如 <b> → 空)。
    • 转换字符(如  → and)。
    • 拼音转换(如 中国 → zhongguo)。

分词器(Tokenizer)

  • 作用:将文本拆分为独立的词元(Token)。
  • 典型实现
    • StandardTokenizer:按空格、标点分割(适用于英文)。
    • IKTokenizer:中文智能分词(如 "搜索引擎" → ["搜索", "引擎"])。

词元过滤器(TokenFilter)

  • 作用:对词元进行进一步处理(过滤、修改、扩展)。
  • 常见过滤器
    • LowerCaseFilter:转小写("Hello" → "hello")。
    • StopFilter:移除停用词(如 "the""is")。
    • SynonymFilter:添加同义词("car" → ["car", "automobile"])。
    • StemmingFilter:词干提取("running" → "run")。

常用内置分析器

分析器类型 行为 适用场景
StandardAnalyzer 按空格/标点分词,转小写,移除英文停用词(如 theis)。 通用英文文本处理
SimpleAnalyzer 按非字母字符分割,转小写,无停用词过滤。 简单分词需求
WhitespaceAnalyzer 仅按空格分割,保留原始大小写。 保留大小写的精确匹配
KeywordAnalyzer 不分词,整个文本作为一个词项。 ID、编码等无需分词的字段
StopAnalyzer 在 SimpleAnalyzer 基础上增加停用词过滤。 需要过滤停用词的简单分词
CJKAnalyzer 对中日韩文本按二元语法(Bigram)分词(如 "中国" → "中", "中国", "国")。 中日韩混合文本(效果有限)

自定义分析器

通过组合 CharFilterTokenizer 和 TokenFilter,可灵活定制分析逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
// 自定义分析器:中文分词 + 转小写 + 拼音转换
Analyzer myAnalyzer = new Analyzer() {
@Override
protected TokenStreamComponents createComponents(String fieldName) {
// 1. 分词器:使用 IK 中文分词器
Tokenizer tokenizer = new IKTokenizer(true); // true 启用智能分词
// 2. 词元过滤器链
TokenStream stream = new LowerCaseFilter(tokenizer); // 转小写
stream = new PinyinFilter(stream); // 添加拼音词项(如 "中国" → "zhongguo")
return new TokenStreamComponents(tokenizer, stream);
}
};

效果

  • 原始文本:"中华人民共和国"
  • 分词结果:["中华", "人民", "共和国"]
  • 拼音扩展:["zhonghua", "renmin", "gongheguo"]
  • 索引词项:中华, 人民, 共和国, zhonghua, renmin, gongheguo
    用途:支持中文关键词和拼音混合搜索(如搜索 "zhonghua" 可匹配到原文)。

倒排索引(Inverted Index)

**倒排索引将文档中的词(Term)映射到包含这些词的文档列表(Posting List)。其核心组成部分包括:

  • 词典(Term Dictionary):存储所有唯一的词(Term),通常以B+树或FST(Finite State Transducer)实现,以便高效查找。
  • 倒排表(Posting List):记录每个词出现的文档ID、词频(TF)、位置信息(Positions)、偏移量(Offsets)等。
  • 文档元数据(Stored Fields):存储原始文档的字段内容(如标题、摘要等),用于搜索结果展示。
  • 规范化的因子(Norms):存储用于计算文档相关性得分的归一化因子(如文档长度)。
  • DocValues:用于排序、聚合等场景的列式存储结构,优化查询性能。

具体数据例子 假设有以下三个文档:

  • Doc1: “hello world”
  • Doc2: “hello lucene”
  • Doc3: “lucene world”

倒排索引构建结果

词项 倒排列表(文档ID、词频、位置)
hello Doc1 (TF=1, Positions:[0]), Doc2 (TF=1, Positions:[0])
world Doc1 (TF=1, Positions:[1]), Doc3 (TF=1, Positions:[1])
lucene Doc2 (TF=1, Positions:[1]), Doc3 (TF=1, Positions:[0])

列式存储结构(DocValues)

DocValues 是 Lucene 在索引阶段将字段的值以「按文档 ID 编号排列」的方式存储下来。这些值是不可变的、持久化到磁盘上的数据结构,主要用于:

  • 排序(sort)
  • 聚合(faceting / grouping)
  • 打分(function query / scoring)
  • 快速访问文档字段值(例如高亮)

为何叫“列式存储”?

普通的倒排索引是词 → 文档ID列表(按词组织,像行式),而 DocValues 是文档ID → 字段值(按字段组织,像列式)。
举个例子,假设有个字段叫 price

DocID price
0 10
1 15
2 13

这个结构就是 DocValues 按列组织的方式,便于系统按 price 排序或做聚合分析。

DocValues 是 Lucene 中为排序和聚合优化的列式存储结构,它以文档为单位存储字段值,适合于高效读取、排序和分析,而不是文本搜索本身。

索引段(Segment)

索引段(Segment) 是 Lucene 索引的基本组成单元,每个段是一个独立的、不可变的子索引,包含完整的倒排索引、存储字段、文档值等数据。Lucene 通过分段机制实现高效的 增量索引写入 和 后台合并优化,是平衡读写性能的核心设计。

索引段的核心特性

  1. 不可变性(Immutable)
    • 段一旦生成,其内容不再修改。新增文档写入新段,删除操作通过标记文档为“已删除”实现。
    • 优势:避免锁竞争,提高并发写入效率;简化缓存机制,提升查询性能。
  2. 分层存储结构
    • 每个段包含独立的文件集(如 .tim.doc.fdt.dvd 等)。
    • 示例:一个索引可能由多个段组成:
      1
      2
      3
      4
      5
      segments_1
      ├── _0.tim # 段0的词项字典
      ├── _0.doc # 段0的倒排列表
      ├── _1.tim # 段1的词项字典
      └── _1.doc # 段1的倒排列表
  3. 段合并(Segment Merging)
    • 自动合并:后台线程将小段合并为大段,减少文件数量,提升查询效率。
    • 清理机制:合并时物理删除标记为“已删除”的文档,释放磁盘空间。

索引段的生命周期

  1. 写入阶段
    • 新文档首先写入 内存缓冲区(RAM Buffer),达到阈值后刷新为磁盘上的新段。
  2. 段合并阶段
    • 当段数超过 segmentsPerTier,触发合并。
    • 根据策略选出多个小段(如段0、段1), 核心词典项、整合倒排索引、重新生成列式存储
    • 删除原始段文件,保留新生成的段。
    • 更新元数据,更新 segments_N 文件,指向新段。
  3. 查询阶段
    • 查询需遍历所有段,合并各段的搜索结果(如取并集、排序)。

索引段的文件结构示例

假设一个索引包含两个段(_0 和 _1),其文件如下:

1
2
3
4
5
6
7
8
9
10
索引目录/
├── segments_3 # 当前活跃段的元数据文件
├── _0.fdt # 段0的存储字段数据
├── _0.fdx # 段0的存储字段索引
├── _0.tim # 段0的词项字典
├── _0.doc # 段0的倒排列表
├── _1.fdt # 段1的存储字段数据
├── _1.fdx # 段1的存储字段索引
├── _1.tim # 段1的词项字典
└── _1.doc # 段1的倒排列表

总结:Lucene 的存储哲学

  • 一切为了查询:倒排索引加速搜索,Doc Values 加速聚合。
  • 不可变性优先:通过段追加和合并实现高吞吐写入。
  • 极致压缩:所有数据均经过编码和压缩,减少存储成本。
  • 分层缓存:依赖操作系统缓存 + 应用层缓存平衡性能与资源消耗。

从文档索引到搜索的步骤

Lucene的索引和搜索过程可以分为两个主要阶段:索引阶段搜索阶段。以下是详细步骤及流程图描述。

索引阶段

索引阶段将原始文档转换为倒排索引结构,涉及以下步骤:

  1. 文档收集:获取一批原始文档(通常是JSON、文本等格式)。
  2. 分词(Tokenization)
    • 使用分析器(Analyzer)对文档字段进行分词,生成Token流。
    • 应用分词器(Tokenizer)和过滤器(TokenFilter,如去停用词、词干提取)。
  3. 构建倒排索引
    • 将分词后的Term映射到文档ID,生成倒排表。
    • 记录词频、位置、偏移等信息。
  4. 存储元数据
    • 将需要显示的字段存储到Stored Fields。
    • 计算Norms并存储。
  5. 写入段(Segment)
    • 将倒排索引、元数据等写入磁盘,生成一个不可变的段。
  6. 段合并(可选)
    • 当段数量过多时,合并小段为大段,减少文件句柄和查询开销。

搜索阶段

搜索阶段根据用户查询检索相关文档,涉及以下步骤:

  1. 查询输入与解析
    • 用户输入查询:用户提供查询字符串,如“Java programming”。
    • 查询解析:Lucene 的 QueryParser 解析查询字符串,根据语法(如布尔运算符、模糊查询、通配符等)生成 Query 对象。例如,“Java AND programming”会被解析为一个布尔查询。
    • 支持多种查询类型:TermQuery(精确匹配)、PhraseQuery(短语匹配)、WildcardQuery(通配符)、FuzzyQuery(模糊匹配)等。
  2. 倒排索引查找
    • 倒排索引结构:Lucene 的核心数据结构是倒排索引,存储了词项(Term)到文档的映射关系。每个词项关联一个倒排列表(Posting List),包含:
      • 包含该词项的文档 ID。
      • 词频(Term Frequency, TF)。
      • 词项在文档中的位置(用于短语查询等)。
    • 查询执行:Query 对象根据类型在倒排索引中查找:
      • 对于 TermQuery,直接查找对应词项的倒排列表,获取匹配的文档 ID。
      • 对于复杂查询(如布尔查询),通过合并多个子查询的结果(交集、并集、差集等)生成最终的文档集。
      • 使用 Collector 收集匹配的文档,通常按评分(Relevance Score)排序。
  3. 评分与排序
    • 评分模型:Lucene 使用基于 TF-IDF(词频-逆文档频率)和其他因素的评分模型(如 BM25)计算文档与查询的相关性:
      • TF(词频):词项在文档中出现的频率越高,相关性越高。
      • IDF(逆文档频率):词项在整个索引中的稀有程度,稀有词权重更高。
      • 字段权重:不同字段(如标题、内容)可设置不同权重。
      • 归一化因子:考虑文档长度等因素。
    • 排序:默认按评分降序排列,也支持自定义排序(如按时间、字段值)。
  4. 结果返回
    • 文档获取:根据收集的文档 ID,从索引中读取文档内容或指定字段(如标题、摘要)。
    • 高亮处理(可选):通过 Highlighter 对匹配的关键词进行标记(如加粗)。
    • 分页:返回指定范围的结果(如前 10 条)。
  5. 性能优化
    • 索引分段:Lucene 将索引分成多个段(Segment),查询时并行处理,提升效率。
    • 缓存:使用 FilterCache 或 FieldCache 缓存常用查询结果或字段值。
    • 跳跃表(Skip List):在倒排列表中快速定位文档,减少 I/O。
    • 布尔查询优化:通过短路逻辑(如优先处理必须匹配的子查询)减少计算量。

常用查询

查询类型 功能描述 典型用法 适用场景 注意事项
TermQuery 精确匹配单个词项 new TermQuery(new Term("title", "Java")) ID、标签、单个关键词搜索 大小写敏感,分词需与索引一致
BooleanQuery 组合子查询,支持 AND、OR、NOT 逻辑 BooleanQuery.Builder 添加 MUST, SHOULD, MUST_NOT 子查询 复杂条件组合、排除特定结果 子查询过多影响性能,需优化
PhraseQuery 匹配连续词项序列(短语),可设置 slop PhraseQuery.Builder 添加词项,设置 slop 精确或近似短语搜索,如“Java programming” slop 越大性能开销越高
WildcardQuery 支持通配符(* 任意字符,? 单字符) new WildcardQuery(new Term("content", "prog*")) 模糊匹配,如“program*” 性能较低,前导通配符(如“*ing”)慎用
FuzzyQuery 模糊匹配,基于编辑距离 new FuzzyQuery(new Term("content", "program"), 2) 拼写错误匹配,如“programe” 性能开销大,限制编辑距离(通常 1 或 2)
TermRangeQuery 匹配字段值在指定范围内的文档 TermRangeQuery.newStringRange("date", "2020-01-01", "2025-12-31", true, true) 日期、数值范围搜索 字段值需可比较,分词可能影响结果
PrefixQuery 匹配以指定前缀开头的词项 new PrefixQuery(new Term("content", "prog")) 自动补全、前缀匹配 性能优于 WildcardQuery,但仍需谨慎
MatchAllDocsQuery 匹配索引中所有文档 new MatchAllDocsQuery() 统计文档总数、导出数据 通常与 Filter 或 BooleanQuery 结合
DisjunctionMaxQuery 多字段查询,取评分最高的子查询 DisjunctionMaxQuery 添加子查询,设置 tie-breaker 多字段搜索,标题/内容匹配优先 调优 tie-breaker,避免评分失衡
BoostQuery 为子查询设置加权,提升/降低评分 new BoostQuery(new TermQuery(new Term("title", "Java")), 2.0f) 调整字段/查询重要性 boost 值需合理,避免评分失真
QueryParser 解析用户输入字符串,生成 TermQuery、BooleanQuery 等 QueryParser parser = new QueryParser("content", analyzer); parser.parse("Java AND programming") 自由文本搜索 分词需一致,防止查询注入

Java示例

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<repositories>
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
</repositories>
<groupId>org.example</groupId>
<artifactId>lucene_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Lucene Core -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>8.0.0</version>
</dependency>
<!-- 查询解析器 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>8.0.0</version>
</dependency>
<dependency>
<groupId>com.github.magese</groupId>
<artifactId>ik-analyzer</artifactId>
<version>8.0.0</version>
</dependency>
<!-- 高亮显示 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>8.0.0</version>
</dependency>

</dependencies>

</project>
1
2
3
4
5
6
7
8
9
10
public class Fields {

static final String TITLE = "title";
static final String CONTENT = "content";
static final String AUTHOR = "author";
static final String LIKE_COUNT = "like_count";
static final String PUBLISH_DATE = "publish_date";
static final String TAGS = "tags";
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.FSDirectory;
import org.wltea.analyzer.lucene.IKAnalyzer;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Date;


public class IndexDemo {
private static final String INDEX_DIR = "lucene_index";

public static void main(String[] args) {
try {
// 1. 创建索引
createIndex();

// 2. 执行复杂查询
search("引擎技术", 10, 0);

} catch (Exception e) {
e.printStackTrace();
}
}

//创建索引
private static void createIndex() throws IOException {
// 设置索引存储目录
FSDirectory directory = FSDirectory.open(Paths.get(INDEX_DIR));
// 使用 IK 中文分词器
IKAnalyzer analyzer = new IKAnalyzer(true);

IndexWriterConfig config = new IndexWriterConfig(analyzer);
config.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
//config.setUseCompoundFile(false);
// 写入数据
try (IndexWriter writer = new IndexWriter(directory, config)) {
// 添加文档1
Document doc1 = new Document();

doc1.add(new TextField(Fields.TITLE, "Lucene搜索引擎技术指南", Field.Store.YES));
doc1.add(new TextField(Fields.CONTENT, "Lucene是一个强大的全文检索引擎库,支持中文分词和复杂查询。", Field.Store.YES));
doc1.add(new StringField(Fields.AUTHOR, "张三", Field.Store.YES));
// 存储字段值(用于查询)
doc1.add(new IntPoint(Fields.LIKE_COUNT, 150));

//启用 DocValues(用于排序)
doc1.add(new NumericDocValuesField(Fields.PUBLISH_DATE, new Date().getTime()));
// 存储原始值(用于展示)
doc1.add(new StoredField(Fields.PUBLISH_DATE, new Date().getTime()));
doc1.add(new StringField(Fields.TAGS, "技术", Field.Store.YES));
doc1.add(new StringField(Fields.TAGS, "搜索", Field.Store.YES));
writer.addDocument(doc1);

// 添加文档2
Document doc2 = new Document();
doc2.add(new TextField(Fields.TITLE, "大数据搜索技术实践", Field.Store.YES));
doc2.add(new TextField(Fields.CONTENT, "结合Elasticsearch和Lucene实现分布式搜索引擎技术。", Field.Store.YES));
doc2.add(new StringField(Fields.AUTHOR, "李四", Field.Store.YES));
doc2.add(new IntPoint(Fields.LIKE_COUNT, 200));
doc2.add(new LongPoint(Fields.PUBLISH_DATE, new Date().getTime()));
doc2.add(new StoredField(Fields.PUBLISH_DATE, new Date().getTime()));
doc2.add(new StringField(Fields.TAGS, "大数据", Field.Store.YES));
doc2.add(new StringField(Fields.TAGS, "分布式", Field.Store.YES));
writer.addDocument(doc2);

writer.commit();
}
}

//执行搜索
private static void search(String queryStr, int topN, int page) throws IOException, ParseException, InvalidTokenOffsetsException {
FSDirectory directory = FSDirectory.open(Paths.get(INDEX_DIR));

try (IndexReader reader = DirectoryReader.open(directory)) {
//搜索实例
IndexSearcher searcher = new IndexSearcher(reader);

// 1. 构建布尔查询
BooleanQuery.Builder boolQuery = new BooleanQuery.Builder();

// 标题或内容匹配关键词(使用 IK 分词)
Analyzer analyzer = new IKAnalyzer(true);
QueryParser parser = new QueryParser(Fields.CONTENT, analyzer);
Query contentQuery = parser.parse(queryStr);
boolQuery.add(contentQuery, BooleanClause.Occur.MUST);

Query likeFilter = IntPoint.newRangeQuery(Fields.LIKE_COUNT, 100, Integer.MAX_VALUE);
boolQuery.add(likeFilter, BooleanClause.Occur.FILTER);

// 2. 排序(按发布时间倒序)
Sort sort = new Sort(new SortedNumericSortField(Fields.PUBLISH_DATE, SortField.Type.LONG, true));

// 3. 分页
TopDocs docs = searcher.search(boolQuery.build(), topN * (page + 1), sort);
ScoreDoc[] hits = docs.scoreDocs;

// 4. 高亮显示
Formatter formatter = new SimpleHTMLFormatter("<b>", "</b>");
Highlighter highlighter = new Highlighter(formatter, new QueryScorer(contentQuery));

System.out.println("找到 " + docs.totalHits.value + " 条结果:");
for (int i = page * topN; i < Math.min(hits.length, (page + 1) * topN); i++) {
Document doc = searcher.doc(hits[i].doc);

// 高亮内容字段
String content = doc.get(Fields.CONTENT);
String highlighted = highlighter.getBestFragment(analyzer, Fields.CONTENT, content);
if (highlighted == null) highlighted = content;

System.out.println("\n标题:" + doc.get(Fields.TITLE));
System.out.println("作者:" + doc.get(Fields.AUTHOR));
System.out.println("内容:" + highlighted);
System.out.println("点赞数:" + doc.get(Fields.LIKE_COUNT));
System.out.println("标签:" + String.join(",", doc.getValues(Fields.TAGS)));
}

}

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
找到 2 条结果:

标题:Lucene搜索引擎技术指南
作者:张三
内容:Lucene是一个强大的全文检索<b>引擎</b>库,支持中文分词和复杂查询。
点赞数:null
标签:技术,搜索

标题:大数据搜索技术实践
作者:李四
内容:结合Elasticsearch和Lucene实现分布式搜索引擎<b>技术</b>。
点赞数:null
标签:大数据,分布式
1
2
3
4
5
6
lucene_index/
├── segments_1 //记录当前索引的所有段信息(段名、文档数、删除文档数、版本等)
├── write.lock //写入锁文件,防止多个进程同时修改索引。
├── _0.cfe //存储 `.cfs` 文件内各子文件的元数据(如子文件的起始位置、长度、名称等)。
├── _0.cfs //存储合并后的所有索引数据(如倒排索引、词条字典、存储字段等)。
├── _0.si //Segment Info 段的元数据文件,记录段的文档数、删除文档、版本等。

社区与资源

由 Hexo 驱动 & 主题 Keep