Lucene索引详解
学习目标:
- 明白索引的过程
- 掌握Lucene索引相关的概念
- 熟练使用索引API创建、更新索引
- 会使用索引查看工具Luke
IndexWriter详解
1、架构图
2、Lucene索引创建API 图示
3、Lucene索引创建代码示例
public static void main(String[] args) throws IOException { //创建使用的分词器 Analyzer analyzer = new IKAnalyzer4Lucene7(true); //索引配置对象 IndexWriterConfig config = new IndexWriterConfig(analyzer); //设置索引库的打开模式:新建、追加、新建或追加 config.setOpenMode(OpenMode.CREATE_OR_APPEND); //索引存放目录 //存放到文件系统中 Directory directory = FSDirectory.open((new File("f:/test/indextest")).toPath()); //存放到内存中 //Directory directory = new RAMDirectory(); //创建索引写对象 IndexWriter writer = new IndexWriter(directory, config); //创建document Document doc = new Document(); //往document中添加 商品id字段 doc.add(new StoredField("prodId", "p0001")); //往document中添加 商品名称字段 String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; doc.add(new TextField("name", name, Store.YES)); }
4、IndexWriter涉及类图示
5、IndexWriterConfig 写索引配置:
- 使用的分词器。
- 如何打开索引(是新建,还是追加)。
- 还可配置缓冲区大小、或缓存多少个文档,再刷新到存储中。
- 还可配置合并、删除等的策略。
注意:用这个配置对象创建好IndexWriter对象后,再修改这个配置对象的配置信息不会对IndexWriter对象起作用。
如要在indexWriter使用过程中修改它的配置信息,通过 indexWriter的getConfig()方法获得 LiveIndexWriterConfig 对象,在这个对象中可查看该IndexWriter使用的配置信息,可进行少量的配置修改(看它的setter方法)
6、Directory 指定索引数据存放的位置:
- 内存
- 文件系统
- 数据库
保存到文件系统用法:
Directory directory = FSDirectory.open(Path path); // path指定目录
7、IndexWriter 用来创建、维护一个索引 。它的API使用流程:
public static void main(String[] args) { //创建索引写对象 IndexWriter writer = new IndexWriter(directory, config); //创建document //将文档添加到索引 writer.addDocument(doc); //删除文档 //writer.deleteDocuments(terms); //修改文档 //writer.updateDocument(term, doc); //刷新 writer.flush(); //提交 writer.commit(); //回滚 //writer.rollback(); //关闭 会提交 writer.close(); }
注意:IndexWriter是线程安全的。
如果你的业务代码中有其他的同步控制,请不要使用IndexWriter作为锁对象,以免死锁。
请查看IndexWriter还提供了哪些:
- add方法
- delete方法
- updatre方法
- 其他方法
8、IndexWriter涉及类图示
Document详解
1、Document 文档
- 要索引的数据记录、文档在lucene中的表示,是索引、搜索的基本单元。一个Document由多个字段Field构成。就像数据库的记录-字段。
- IndexWriter按加入的顺序为Document指定一个递增的id(从0开始),称为文档id。反向索引中存储的是这个id,文档存储中正向索引也是这个id。
- 业务数据的主键id只是文档的一个字段。
2、Document API
3、Field
- 字段:由字段名name、字段值value(fieldsData)、字段类型 type 三部分构成。
- 字段值可以是文本(String、Reader 或 预分析的 TokenStream)、二进制值(byte[])或数值。
4、IndexableField Field API
5、Document—Field 数据举例
- 新闻:新闻id,新闻标题、新闻内容、作者、所属分类、发表时间
- 网页搜索的网页:标题、内容、链接地址
- 商品: id、名称、图片链接、类别、价格、库存、商家、品牌、月销量、详情…
6、IndexableFieldType
- 字段类型:描述该如何索引存储该字段
字段可选择性地保存在索引中,这样在搜索结果中,这些保存的字段值就可获得。
一个Document应该包含一个或多个存储字段来唯一标识一个文档。
注意:未存储的字段,从索引中取得的document中是没有这些字段的。
7、 Document 类关系
8、IndexableFieldType API 说明
9、IndexOptions 索引选项说明:
- NONE 不索引(Not indexed)
- DOCS 反向索引中只存储了包含该词的 文档id,没有词频、位置
- DOCS_AND_FREQS 反向索引中会存储 文档id、词频
- DOCS_AND_FREQS_AND_POSITIONS 反向索引中存储 文档id、词频、位置
- DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS 反向索引中存储 文档id、词频、位置、偏移量
10、Luke 索引查看工具安装
为了查看我们的代码创建的索引情况,请安装工具 luke:
下载地址(当前最新版 7.2.0 可用于lucene7.3.0版):
https://github.com/DmitryKey/luke/releases
11、luke Document 查看说明:
12、IndexableFieldType API 说明:
13、storeTermVectors
对于不需要在搜索反向索引时用到,但在搜索结果处理时需要的位置、偏移量、附加数据(payLoad) 的字段,我们可以单独为该字段存储(文档id词项向量)的正向索引。
- boolean storeTermVectors() 是否存储词项向量
- boolean storeTermVectorPositions() 是否在词项向量中存储位置
- boolean storeTermVectorOffsets() 是否在词项向量中存储偏移量
- boolean storeTermVectorPayloads() 是否在词项向量中存储附加信息
FieldType实现类中有对应的set方法
概念说明:Token trem 词条: 分词得到的词项
14、DocValuesType 选项说明
- NONE 不开启docvalue
- NUMERIC 单值、数值字段,用这个
- BINARY 单值、字节数组字段用
- SORTED 单值、字符字段用, 会预先对值字节进行排序、去重存储
- SORTED_NUMERIC 单值、数值数组字段用,会预先对数值数组进行排序
- SORTED_SET 多值字段用,会预先对值字节进行排序、去重存储
具体使用选择:
- 字符串+单值 会选择SORTED作为docvalue存储
- 字符串+多值 会选择SORTED_SET作为docvalue存储
- 数值或日期或枚举字段+单值 会选择NUMERIC 作为docvalue存储
- 数值或日期或枚举字段+多值 会选择SORTED_SET作为docvalue存储
强调:需要排序、分组、聚合、分类查询(面查询)的字段才创建docValues
15、什么是附加信息Payloads
附加信息非常有用,可用它来存储特殊信息,及减少词项数等
16、如何加入数值字段
请查看Field类中提供了对应的构造方法或其他方法没?
Field的构造方法和set方法:
构造方法没有对应的,set方法倒是有,请看下setIntValue方法的源码,看它是如何将字段值设为一个整数值的,你有疑惑吗? 再看下其他的set方法。
只有一种解释:数值字段需要扩展Field 。该如何扩展呢?
请再查看 IndexableField 的API :
请再查看Field类中对应的实现
你是否发现它们的实现和那些setValue方法的逻辑是一样的。
疑问1:它们为什么返回null,而不是进行转换后返回?
疑问2:为什么有这五个取值方法?你是否有答案?
回顾前面 Field 的定义:
- 字段:由字段名name、字段值value、字段类型 type 三部分构成。
- 字段值可以是文本(String、Reader 或 预分析的 TokenStream)、二进制值(byte[])或数值。
这两个疑问你解开了吗?
- 五个方法对应五种不同的值类型
- 返回null表示不是该类型的值
- 还有一点待发现总结
不然,IndexWriter在索引字段时,如何知道字段的值类型。但在IndexWirter中它是以怎样的顺序来进行判断的呢?
查看 IndexWriter在索引、存储字段、为字段建立docValues正向索引时的代码,看看它是如何使用这些方法的,找出答案。
补充疑惑答案一点:
- 即使不是binary类型值,也要提供正确的binaryValue()值获取
加入数值字段方式:
- 扩展Field,提供构造方法传入数值类型值,赋给字段值字段;
- 改写binaryValue() 方法,返回数值的字节引用。
17、Lucene预定义的字段子类,你可灵活选用
- TextField: Reader or String indexed for full-text search
- StringField: String indexed verbatim as a single token
- IntPoint: int indexed for exact/range queries.
- LongPoint: long indexed for exact/range queries.
- FloatPoint: float indexed for exact/range queries.
- DoublePoint: double indexed for exact/range queries.
- SortedDocValuesField: byte[] indexed column-wise for sorting/faceting
- SortedSetDocValuesField: SortedSet<byte[]> indexed column-wise for sorting/faceting
- NumericDocValuesField: long indexed column-wise for sorting/faceting
- SortedNumericDocValuesField: SortedSet<long> indexed column-wise for sorting/faceting
- StoredField: Stored-only value for retrieving in summary results
请仔细看它们的源码是怎么设置字段的值、类别的。
注意:这里没有设置存储词项向量的。如果单个子类不满足需要,可多个组合。
如果组合不了,就直接用Field + FieldType
索引更新
1、IndexWriter 索引更新 API
2、说明:
- Term 词项 指定字段的词项
- 删除流程:根据Term、Query找到相关的文档id、同时删除索引信息,再根据文档id删除对应的文档存储
- 更新流程:先删除、再加入新的doc
- 注意:只可根据索引的字段进行更新
1、空间换时间
对这种需要排序、分组、聚合的字段,为其建立独立的文档->字段值的正向索引、列式存储。这样我们要加载搜中文档的这个字段的数据就快很多,耗内存少。
2、docValuesType
IndexableFieldType 中的 docValuesType方法 就是让你来为需要排序、分组、聚合的字段指定如何为该字段创建文档->字段值的正向索引的。