1. Lucene高级搜索
1.1 文本搜索
QueryParser支持默认搜索域, 第一个参数为默认搜索域。
如果在执行parse方法的时候,查询语法中包含域名则从指定的这个域名中搜索, 如果只有查询的关键字,则从默认搜索域中搜索结果。
需求描述 : 查询名称中包含华为手机关键字的结果。
测试代码:
1 |
|
1.2 数值范围搜索
需求描述 : 查询价格大于等于100, 小于等于1000的商品
测试代码:
1 | /** |
1.3 组合搜索
需求描述 : 查询价格大于等于100, 小于等于1000, 并且名称中不包含华为手机关键字的商品
BooleanClause.Occur.MUST
必须 相当于and, 并且
BooleanClause.Occur.MUST_NOT
不必须 相当于not, 非
BooleanClause.Occur.SHOULD
应该 相当于or, 或者
注意 : 如果逻辑条件中, 只有MUST_NOT, 或者多个逻辑条件都是MUST_NOT, 无效, 查询不出任何数据.
1 | /** |
2. 搜索案例
成品效果:
2.1 引入依赖
在项目的pom.xml中引入依赖:
1 | <properties> |
2.2 项目加入页面和资源
将Lucene课程资料\资源\页面和静态资源, 下的页面和静态资源拷贝到项目的resources目录下
2.3 创建包和启动类
创建目录, 并加入启动类:
启动类代码:
1 | /** |
2.4 配置文件
项目的resources目录下创建application.yml内容如下:
1 | spring: |
2.5 业务代码
2.5.1 封装pojo
pojo包下加入ResultModel实体类
1 | /** |
2.5.2 controller代码
1 | /** |
2.5.3 service代码
service接口:
1 | /** |
service实现类:
1 | /** |
3. Lucene 底层储存结构(高级)
3.1 详细理解lucene存储结构
存储结构 :
索引 (Index) :
- 一个目录一个索引,在 Lucene中一个索引是放在一个文件夹中的。
段(Segment) :
- 一个索引 (逻辑索引)由多个段组成, 多个段可以合并, 以减少读取内容时候的磁盘IO.
- Lucene 中的数据写入会先写内存的一个Buffer,当Buffer内数据到一定量后会被flush成一个Segment,每个Segment有自己独立的索引,可独立被查询,但数据永远不能被更改。这种模式避免了随机写,数据写入都是批量追加,能达到很高的吞吐量。Segment中写入的文档不可被修改,但可被删除,删除的方式也不是在文件内部原地更改,而是会由另外一个文件保存需要被删除的文档的DocID,保证数据文件不可被修改。Index的查询需要对多个Segment进行查询并对结果进行合并,还需要处理被删除的文档,为了对查询进行优化,Lucene会有策略对多个Segment进行合并。
文档(Document) :
- 文档是我们建索引的基本单位,不同的文档是保存在不同的段中的,一个段可以包含多篇文档。
- 新添加的文档是单独保存在一个新生成的段中,随着段的合并,不同的文档合并到同一个段中。
域(Field) :
- 一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,描述等,都可以保存在不同的域里。
- 不同域的索引方式可以不同。
词(Term) :
- 词是索引的最小单位,是经过词法分析和语言处理后的字符串。
3.2 索引库物理文件
3.3 索引库文件扩展名对照表
名称 | 文件扩展名 | 简短描述 |
---|---|---|
Segments File | segments_N | 保存了一个提交点(a commit point)的信息 |
Lock File | write.lock | 防止多个IndexWriter同时写到一份索引文件中 |
Segment Info | .si | 保存了索引段的元数据信息 |
Compound File | .cfs,.cfe | 一个可选的虚拟文件,把所有索引信息都存储到复合索引文件中 |
Fields | .fnm | 保存fields的相关信息 |
Field Index | .fdx | 保存指向field data的指针 |
Field Data | .fdt | 文档存储的字段的值 |
Term Dictionary | .tim | term词典,存储term信息 |
Term Index | .tip | 到Term Dictionary的索引 |
Frequencies | .doc | 由包含每个term以及频率的docs列表组成 |
Positions | .pos | 存储出现在索引中的term的位置信息 |
Payloads | .pay | 存储额外的per-position元数据信息,例如字符偏移和用户payloads |
Norms | .nvd,.nvm | .nvm文件保存索引字段加权因子的元数据,.nvd文件保存索引字段加权数据 |
Per-Document Values | .dvd,.dvm | .dvm文件保存索引文档评分因子的元数据,.dvd文件保存索引文档评分数据 |
Term Vector Index | .tvx | 将偏移存储到文档数据文件中 |
Term Vector Documents | .tvd | 包含有term vectors的每个文档信息 |
Term Vector Fields | .tvf | 字段级别有关term vectors的信息 |
Live Documents | .liv | 哪些是有效文件的信息 |
Point values | .dii,.dim | 保留索引点,如果有的话 |
3.4 词典的构建
为何Lucene大数据量搜索快, 要分两部分来看 :
- 一点是因为底层的倒排索引存储结构 .
- 另一点就是查询关键字的时候速度快 , 因为词典的索引结构.
3.4.1 词典数据结构对比
倒排索引中的词典位于内存,其结构尤为重要,有很多种词典结构,各有各的优缺点,最简单如排序数组,通过二分查找来检索数据,更快的有哈希表,磁盘查找有B树、B+树,但一个能支持TB级数据的倒排索引结构需要在时间和空间上有个平衡,下图列了一些常见词典的优缺点:
数据结构 | 优缺点 |
---|---|
跳跃表 | 占用内存小,且可调,但是对模糊查询支持不好 |
排序列表Array/List | 使用二分法查找,不平衡 |
字典树 | 查询效率跟字符串长度有关,但只适合英文词典 |
哈希表 | 性能高,内存消耗大,几乎是原始数据的三倍 |
双数组字典树 | 适合做中文词典,内存占用小,很多分词工具均采用此种算法 |
Finite State Transducers (FST) | 一种有限状态转移机,Lucene 4有开源实现,并大量使用 |
B树 | 磁盘索引,更新方便,但检索速度慢,多用于数据库 |
Lucene3.0之前使用的也是跳跃表结构,后换成了FST,但跳跃表在Lucene其他地方还有应用如倒排表合并和文档号索引。
3.4.2 跳跃表原理
Lucene3.0版本之前使用的跳跃表结构后换成了FST结构
优点 :结构简单、跳跃间隔、级数可控,Lucene3.0之前使用的也是跳跃表结构,,但跳跃表在Lucene其他地方还有应用如倒排表合并和文档号索引。 缺点 :模糊查询支持不好.
单链表 :
单链表中查询一个元素即使是有序的,我们也不能通过二分查找法的方式缩减查询时间。
通俗的讲也就是按照链表顺序一个一个找.
举例: 查找85这个节点, 需要查找7次.
跳跃表 :
举例: 查询85这个节点, 一共需要查询6次.
- 在level3层, 查询3次, 查询到1结尾, 退回到37节点
- 在level2层, 从37节点开始查询, 查询2次, 查询到1结尾, 退回到71节点
- 在level1层, 从71节点开始查询, 查询1次, 查询到85节点.
3.4.3 FST原理简析
Lucene现在采用的数据结构为FST,它的特点就是:
- 优点:内存占用率低,压缩率一般在 3 倍~20倍之间、模糊查询支持好、查询快
- 缺点:结构复杂、输入要求有序、更新不易
已知FST要求输入有序,所以Lucene会将解析出来的文档单词预先排序,然后构建FST,我们假设输入为abd,abe,acf,acg,那么整个构建过程如下:
输入数据:
1 | String inputValues[] = {"hei","ma","cheng","xu","yuan","good"}; |
输入的数据如下 :
hei/0 ma/1 cheng/2 xu/3 yuan/4 good/5
存储结果如下:
4. Lucene 优化(高级)
4.1 解决大量磁盘IO
config.setMaxBufferedDocs(100000); 控制写入一个新的segment前内存中保存的document的数目,设置较大的数目可以加快建索引速度。
数值越大索引速度越快, 但是会消耗更多的内存
indexWriter.forceMerge( 文档数量); 设置N个文档合并为一个段
数值越大索引速度越快, 搜索速度越慢; 值越小索引速度越慢, 搜索速度越快
更高的值意味着索引期间更低的段合并开销,但同时也意味着更慢的搜索速度,因为此时的索引通常会包含更多的段。如果该值设置的过高,能获得更高的索引性能。但若在最后进行索引优化,那么较低的值会带来更快的搜索速度,因为在索引操作期间程序会利用并发机制完成段合并操作。故建议对程序分别进行高低多种值的测试,利用计算机的实际性能来告诉你最优值。
创建索引代码优化测试:
1 | /** |
4.2 选择合适的分词器
不同的分词器分词效果不同, 所用时间也不同
虽然StandardAnalyzer切分词速度快过IKAnalyzer, 但是由于StandardAnalyzer对中文支持不好, 所以为了追求好的分词效果, 为了追求查询时的准确率, 也只能用IKAnalyzer分词器, IKAnalyzer支持停用词典和扩展词典, 可以通过调整两个词典中的内容, 来提升查询匹配的精度
4.3 选择合适的位置存放索引库
类 | 写操作 | 读操作 | 特点 |
---|---|---|---|
SimpleFSDirectory | java.io.RandomAccessFile | java.io.RandomAccessFile | 简单实现,并发能力差 |
NIOFSDirectory | java.nio.FileChannel | FSDirectory.FSIndexOutput | 并发能力强,windows平台下有重大bug |
MMapDirectory | 内存映射 | FSDirectory.FSIndexOutput | 读取操作基于内存 |
测试代码修改:
1 | Directory directory = MMapDirectory.open(Paths.get("E:\\LuceneDir")); |
4.4 搜索api的选择
1、尽量使用TermQuery代替QueryParser
2、尽量避免大范围的日期查询
5. Lucene 相关度排序(高级)
5.1 什么是相关度排序
Lucene对查询关键字和索引文档的相关度进行打分,得分高的就排在前边。
5.1.1 如何打分
Lucene是在用户进行检索时实时根据搜索的关键字计算出来的,分两步:
1、计算出词(Term)的权重
2、根据词的权重值,计算文档相关度得分。
5.1.2 什么是词的权重
明确索引的最小单位是一个Term(索引词典中的一个词),搜索也是要从Term中搜索,再根据Term找到文档,Term对文档的重要性称为权重,影响Term权重有两个因素:
Term Frequency (tf) : 指此Term在此文档中出现了多少次。tf 越大说明越重要。 词(Term)在文档中出现的次数越多,说明此词(Term)对该文档越重要,如“Lucene”这个词,在文档中出现的次数很多,说明该文档主要就是讲Lucene技术的。
Document Frequency (df) : 指有多少文档包含次Term。df 越大说明越不重要。 比如,在一篇英语文档中,this出现的次数更多,就说明越重要吗?不是的,有越多的文档包含此词(Term), 说明此词(Term)太普通,不足以区分这些文档,因而重要性越低。
5.1.3 怎样影响相关度排序
boost是一个加权值(默认加权值为1.0f),它可以影响权重的计算。
- 在索引时对某个文档中的 field设置加权值高,在搜索时匹配到这个文档就可能排在前边。
- 在搜索时对某个域进行加权,在进行组合域查询时,匹配到加权值高的域最后计算的相关度得分就高。
设置boost是给域(field)或者Document设置的
5.2 人为影响相关度排序
查询的时候, 通过设置查询域的权重, 可以人为影响查询结果.
1 | /** |
6. Lucene 使用注意事项(高级)
- 关键词区分大小写 OR AND TO等关键词是区分大小写的,lucene只认大写的,小写的当做普通单词。
- 读写互斥性 同一时刻只能有一个对索引的写操作,在写的同时可以进行搜索
- 文件锁 在写索引的过程中强行退出将在tmp目录留下一个lock文件,使以后的写操作无法进行,可以将其手工删除
- 时间格式 lucene只支持一种时间格式yyMMddHHmmss,所以你传一个yy-MM-dd HH:mm:ss的时间给lucene它是不会当作时间来处理的
- 设置 boost 有些时候在搜索时某个字段的权重需要大一些,例如你可能认为标题中出现关键词的文章比正文中出现关键词的文章更有价值,你可以把标题的boost设置的更大,那么搜索结果会优先显示标题中出现关键词的文章.
-------------本文结束感谢您的阅读-------------
本文标题: Lucene(二)
本文链接: https://wgy1993.gitee.io/archives/6c0148b0.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 进行许可。转载请注明出处!
