基础:从概念理解Lucene的Index(索引)文档模型
转:http://blog.csdn.net/duck_genuine/article/details/6053430
Lucene主要有两种文档模型:Document和Field,一个Document可能包含若干个Field。
每一个Field有不同的策略:
1.被索引 or not,将该字段(Field)经过分析(Analyisi)后,加入索引中,并不是原文 。
2.如果被索引,可选择是否保存“term vector”(向量),用于相似检索。
3.可选择是否存储(store),将原文直接拷贝 ,不做索引,用于检索后的取出。
Lucene中的文档模型类似于数据库,但是又不完全相同,体现在如下几方面:
1.无规范格式,即无需固定的Schema,无列等预先设计,同一个索引中加入的Document可包含不同的Field 。
2.非正规化,Lucene中的文档模型是一个平面化 的结构,没有递归定义,自然连接等等复杂的结构。
2.2 理解索引过程
总体来说,索引过程为:
1.提取摘要:从原文提取,并创建Document和Field对象。Tika 提供了PDF、Word等非文本的文本提取。
2.分析:Analysis,首先对Document的Field进行分解,产生token流,然后经过一系列Filter(如小写化)等。
3.建立索引:通过IndexWriter的addDocument写入到索引中。Lunece使用了反向索引 ,即“那个Document包含单词X”,而不是“Document包含哪些Word”
索引文件组成
为了保证效率,每个索引由若干segments组成:
_X.cfs 每个segments由若干个cfs组成,X为0,1,2….如果开启了useCompoundFile,则只有一个.cfs文件。
segments_<N>:记载每个分区对应的cfs文件。
每个一段时间后,在调用IndexWriter时,会自动合并这些segment
2.3 索引的基本操作
首先创建IndexWriter
IndexWriter(dir,new WhiteSpaceAnalyser(),IndexWriter.MaxField.UNLIMITED);
dir是索引的保存路径,WhiteSpaceAnalyser是基于空白的分词 ,最后部限定Field的数量。
依次创建文档Document和Field
Document doc = new Document();
doc.add(new Filed(key,value,STORE?,INDEX?)
key就是field的检索字段名,value就是待写入/分析的文本。
STORE ,与索引无关,是否额外存储原文 ,可以在搜索结果后调用出来,NO不额外存储;YES,额外存储。
INDEX ,NO,不索引;ANALYZED,分词后索引;NOT_ANALYZED,不分词索引;ANALYZED_NO_NORMS,分词索引,不存储NORMS;NOT_ANALYZED_NO_NORMS,不分词,索引,不存储NORMS。除了NO外都算索引,可以搜索 。NORMS存储了boost所需信息,包含了NORM可能会占用更多内存?
删除索引
IndexWriter提供了删除Document的功能:
deleteDocumen(Term)
deleteDocumen(Term[])
deleteDocumen(Query)
deleteDocumen(Query [])
特别注意Term不一定是唯一的,所以有可能误删除多个 。另外最好选择唯一的、非索引的Term 以防混乱(比如唯一ID)。
删除后commit()然后close才能真正写入索引文件中。
删除后只是标记为删除,maxDoc()返回所有文档(含已经删除,但未清理的);numDocs:未删除的文档数量
使用delete后,再optimize():压缩删除的空间、再commit才真正的删除释放空间。
更新索引
updateDocument(Term,Document),Lunce只支持全部替换,即整个Docuemnt要被替换掉,没法更新单独的Field。
2.4 Field的选项
选项分为三类:index、storing和term vector。
Index选项
Index.ANALYZED :分词后索引
Index.NOT_ANALYZED : 不分词直接索引,例如URL、系统路径等,用于精确检索 。
Index.ANALYZED_NO_NORMS : 类似Index.ANALYZED,但不存储NORM TERMS,节约内存但不支持Boost。
Index.NOT_ANALYZED_NO_NORMS : 类似Index.NOT_ANALYZED,但不存储NORM TERMS,节约内存但不支持Boost,非常常用 。
Index.NO : 根本不索引,所以不会被检索到
默认情况,Luncene会存储所有单词的出现位置,可以用Field.setOmitTermFreqAndPositions(true)关闭,但是会影响PhraseQuery和SpanQuery。
Store选项
Store.YES :存储原始value数值,可在检索后被提取 。
Store.NO :不存储原始数值,检索后无法重新提取。
CompressionTools 可用于压缩、解压缩byte数组。
Term Vector选项
Term Vector主要用于为相似搜索 提供支持 ,例如搜索cat,返回cat。
TermVector.YES :记录Term Vector
TermVector.WITH_POSITIONS :记录Term Vector以及每个Term出现的位置
TermVector.WITH_OFFSETS :记录Term Vector以及每个Term出现的偏移
TermVector.WITH_POSITIONS_OFFSETS :记录Term Vector以及出现的位置+偏移
TermVector.NO :不存储TermVector
如果Index选择了No,则TermVector必须选择No
将String外的类型作为Field的数据源
Reader:无法被STORE,默认TokenStream始终被分词和索引。
TokenStream:分词之后的结果作为源,无法被Store,始终analyzed并索引。
byte[] :无法被索引,没有TermVector,必须被Store.YES
与排序相关选项
数字Field可以用NumericField,如果是文本Field必须Field.Index.NOT_ANALYZED,才能排序,即保证这个Field只含有一个Token才能排序 。
多值Field(Multi-valued Fields)
比如一本书有多个作者,怎么办呢?
一种方法是,添加多个同一key,不同value的Field
Document doc = new Document();
for (int i = 0; i < authors.length; i++) {
doc.add(new Field(“author”, authors[i],
Field.Store.YES,
Field.Index.ANALYZED));
}
还有一种方法在第4章中提出。
2.5 Boost(提升)
boost可以对影响搜索返回结果的排序 。
boost可以在index或者搜索时候完成,后者更具有灵活性可独立制定但耗费更多CPU。
Booost Doument
index时候boost将存储在NORMS TERM中。默认情况下,所有Document有相等的Boost,即1.0,可以手动提升一个Docuemnt的Boost数值。
Document.settBoost(float bei),bei是1.0的倍数。
Boost Field
也可以对Field进行索引,使用Document的Boost,对下属的Field都执行相同的Field。
单独对Field进行Boost
Field.boost(float)
注意:Lucene的Rank算法由多种因素组成,Boost只是一个因素之一,不是决定性因素 。
Norms
boost的数值存储在Norms中,可能会导致Search时占用大量内存。因此可将其关闭:
设置NO_NORMS,或者再Field中指定Field.setOmitNorms(true)。
2.6 对数字、日期、时间等进行索引
索引数字
有两种场景:
1.数字嵌入在Text中,例如“Be sure to include Form 1099 in your tax return”,而你想要搜索1099这个词。此时需要选择不分解数字的Analyzer ,例如WhitespaceAnalyzer或者StandardAnalyzer。而SimpleAnalyzer和StopAnalyzer会忽略数字,无法通过1099检出。
2.数字式单独的Field,2.9之后,Lucene支持了数字类型,使用NumericField即可:doc.add(new NumericField(“price”).setDoubleValue(19.99));此时,对数字Field使用字典树存储,
可向document中添加一样的NumericField数值,在NumericRangeQuery、NumericRangeFilter中以or的方式支持,但是排序中不支持。因此如果要排序,必须添加唯一的NumericField。
precisionStep控制了扫描精度,越小越精确但速度越慢。
索引日期和时间
方法是:将日期转化为时间戳(长整数) ,然后按照NumericField进行处理。
或者,如果不需要精确到毫秒,可以转化成秒处理
doc.add(new NumericField(“day”) .setIntValue((int) (new Date().getTime()/24/3600)));
甚至对某一天进行索引而不是具体时间。
Calendar cal = Calendar.getInstance();
cal.setTime(date);
doc.add(new NumericField(“dayOfMonth”)
.setIntValue(cal.get(Calendar.DAY_OF_MONTH)));
2.7 Field截断
Lucene支持对字段的截断。IndexWriter.MaxFieldLength表示字段的最大长度,默认为MaxFieldLength.UNLIMITED,无限。
而MaxFieldLength.LIMITED表示有限制,可以通过setMaxFieldLength(int n)进行指定。
上述设定之后,只保留前n个字符。
可以通过setInfoStream(System.out)获得详细日志信息。
2.8 实时搜索
2.9后支持实时搜索,或者说很快的入索引–检索过程 。
IndexReader IndexWriter.getReader()
本方法将立即刷新Index的缓存,生效后立即返回IndexReader用于搜索。
2.9 优化索引
索引优化可以提升搜索速度 ,而非索引速度。它指的是将小索引文件合并成几个。
IndexWriter提供了几个优化方法:
optimize():将索引合并为一个段,完成前不会返回。但是太耗费资源。
optimize(int maxNumSegments):部分优化,优化到最多maxNumSegments个段?是优化于上述极端情况的这种,例如5个。
optimize(boolean doWait):通optimize(),但是它将立即返回。
optimize(int maxNumSegments, boolean doWait):同optimize(int maxNumSegments),但是将立即返回。
另外:在优化中会耗费大量的额外空间 。即旧的废弃段直到IndexWriter.commit()之后才能被移除 。
2.10 Directory
Directory封装了存储的API,向上提供了抽象的接口,有以下几类:
SimpleFSDirectory:存储于本地磁盘使用java.io,不支持多线程,要自己加锁 。
NIOFSDirectory:多线程可拓展,使用java.nio,支持多线程安全,但是Windows下有Bug 。
MMapDirectory:内存映射存储(将文件映射到内存中进行操作,类似nmap)。
RAMDirectory:全部在内存中存储。
FileSwitchDirectory:使用两个目录,切换交替使用。
使用FSDirectory.open将自动挑选合适的Directory。也可以自己指定:
Directory ramDir = new RAMDirectory();
IndexWriter writer = new IndexWriter(ramDir, analyzer, IndexWriter.MaxFieldLength.UNLIMITED);
RAMDirectory适用于内存比较小的情况。
可以拷贝索引以用于加速:
Directory ramDir = new RAMDirectory(otherDir);
或者
Directory.copy(Directory sourceDir,
Directory destDir,
boolean closeDirSrc);
2.11 线程安全、锁
线程、多JVM安全
任意多个IndexReaders可同时打开,可以跨JVM。
同一时间 ,只能打开一个 IndexWriter,独占写锁 。内建线程安全机制。
IndexReaders可以在IndexWriter打开的时候打开。
多线程间可共享IndexReader或者IndexWriter,他们是线程安全的,内建同步机制且性能较高。
通过远程文件系统共享IndexWriter
注意不要反复打开、关闭,否则会影响性能。
Index的锁
以文件锁的形式,名为write.lock。
如果在已经被锁定的情况下再创建一个IndexWriter,会遇到LockObtainFailedException。
也支持其他锁定方式,但是一般情况下无需改变它们。
IndexWriter.isLocked(Directory):检查某目录是否被锁。
IndexWriter.unlock(Directory):对某目录解锁,危险!。
注意!每次IndexWriter无论执行了什么操作,都要显示的close !不会自动释放锁的!
2.12 调试索引
2.14 高级的索引选项
IndexReader可以用来彻底删除已经去除的Index,优点如下:
1.通过Document的具体Number来删除,更精确而IndexWriter不行。
2.IndexReader可以在删除后立即显示出来,而IndexWriter必须重新打开才能显示出来。
3.IndexReader拥有undeleteAll,可以撤销所有删除的索引(只对尚未merged的有效 )。
释放删除索引后的空间
可以调用expungeDeletes显示的释放空间,它将执行merge从而释放删除但仅仅做了标记,尚未释放的空间。
缓存和刷新
当添加索引、删除索引时候,在内存中建立了一个缓存以减少磁盘I/O,Lucene会定期把这些缓存中的改动放入Directory中便形成了一个segment(段)。
IndexWriter刷新缓存的条件是:
当内存中数据已经大于setRAMBufferSizeMB的指定。
当索引中的Document数量多于setMaxBufferedDocs的指定。
当索引被删除的数量多于setMaxBufferedDeleteTerms的指定。
上述条件之一发生时,即触发缓存刷进,它将建立新的Segment但不存入磁盘,只有当commit后才写入磁盘的index。
索引的commit
commit将改动持久化到本次索引中。只有调用commit后,再打开的IndexReader或者IndexSearcher才能看到最近一次commit之后的结果。
关闭close也将间接调用commit。
与commit相对的是rollback方法,它将撤销上次commit之后的所有改动。
commit非常耗时,不能经常调用。
“双缓冲”的commit
在图形界面开发中,经常有双缓冲技术,即一个用于被刷新,一个用于显示,两个之间互换使用。Lucene也支持这样的机制。
Lucene暴露了两个接口:
prepareCommit
Commit
prepareCommit比较慢,而调用prepareCommit后再调用Commit则会非常快。
删除策略
IndexDeletionPolicy决定了删除策略。可以决定是否保留之前的commit版本。
Lucene对ACID的事务支持
这主要是通过“同时只能打开一个IndexWriter”来实现的。
如果JVM、OS或者机器挂了,Lucene会自动恢复到上一个commit版本。
合并Merge
当索引有过多的Segnmnet的时候,需要进行合并Merge。优点:
1.减少了Segnment的文件数量
2.减少索引文件占用的空间大小。
MERGEPOLICY决定何时需要执行合并Merge
MERGEPOLICY
选择那些文件需要被合并,默认有两种策略:
LogByteSizeMergePolicy :根据Index大小决定是否需要合并
LogDocMergePolicy :根据Document的数量决定是否需要合并
分别通过
setMergeFactor
和setMaxMergeDocs来指定,具体参数见API。
MERGESCHEDULER
决定如何进行合并:
ConcurrentMergeScheduler,后台额外线程进行合并,可通过waitForMerges得知合并完成。
SerialMergeScheduler,在addDocument时候串行合并,使用统一线程。
基础:从概念理解Lucene的Index(索引)文档模型的更多相关文章
- lucene全文搜索之三:生成索引字段,创建索引文档(给索引字段加权)基于lucene5.5.3
前言:上一章中我们已经实现了索引器的创建,但是我们没有索引文档,本章将会讲解如何生成字段.创建索引文档,给字段加权以及保存文档到索引器目录 luncene5.5.3集合jar包下载地址:http:// ...
- Lucene7.2.1系列(二)luke使用及索引文档的基本操作
系列文章: Lucene系列(一)快速入门 Lucene系列(二)luke使用及索引文档的基本操作 Lucene系列(三)查询及高亮 luke入门 简介: github地址:https://githu ...
- 分布式搜索elasticsearch 索引文档的增删改查 入门
1.RESTful接口使用方法 为了方便直观我们使用Head插件提供的接口进行演示,实际上内部调用的RESTful接口. RESTful接口URL的格式: http://localhost:9200/ ...
- head插件对elasticsearch 索引文档的增删改查
1.RESTful接口使用方法 为了方便直观我们使用Head插件提供的接口进行演示,实际上内部调用的RESTful接口. RESTful接口URL的格式: http://localhost:9200 ...
- Elasticsearch 索引文档的增删改查
利用Elasticsearch-head可以在界面上(http://127.0.0.1:9100/)对索引进行增删改查 1.RESTful接口使用方法 为了方便直观我们使用Head插件提供的接口进行演 ...
- Elasticsearch-如何控制存储和索引文档(_source、_all、返回源文档的某些字段)
Elasticsearch-如何控制存储和索引文档(_source._all) _source:可以在索引中存储文档._all:可以在单个字段上索引所有内容. 1. 存储原有内容的_source _s ...
- Elasticsearch必知必会的干货知识一:ES索引文档的CRUD
若在传统DBMS 关系型数据库中查询海量数据,特别是模糊查询,一般我们都是使用like %查询的值%,但这样会导致无法应用索引,从而形成全表扫描效率低下,即使是在有索引的字段精确值查找,面对海量数 ...
- 详细描述一下 Elasticsearch 索引文档的过程 ?
面试官:想了解 ES 的底层原理,不再只关注业务层面了. 解答: 这里的索引文档应该理解为文档写入 ES,创建索引的过程. 文档写入包含:单文档写入和批量 bulk 写入,这里只解释一下:单文档写入流 ...
- 详细描述一下 Elasticsearch 索引文档的过程 ?
这里的索引文档应该理解为文档写入 ES,创建索引的过程. 文档写入包含:单文档写入和批量 bulk 写入,这里只解释一下:单文档写入流程. 记住官方文档中的这个图. 第一步:客户写集群某节点写入数据, ...
随机推荐
- Day08_面向对象第三天
1.代码块(掌握) 1.概述 由{}扩起来的代码称之为代码块,类或者方法也可认为是代码块,但是一般不这么说,我们平时所说的代码块指的是孤零零的{} 2.代码块作用 局部代码块作用 ...
- Intent传输包含对象的List集合
这个其实也比较简单,我也是参考了网上的一些例子,不过我写的这个小例子亲测可用.用实现Serializable接口的方式实现. 就是说,你的list集合中的对象必须先实现Serializable接口,其 ...
- Think Python - Chapter 12 Tuples
12.1 Tuples are immutable(元组是不可变的)A tuple is a sequence of values. The values can be any type, and t ...
- 动态数组 - ArrayList
前言 如果数组的大小要随时间变化,那么数组操作起来就比较麻烦. 在C++中,这种情况要用到动态向量Vector. 而Java中,提供了一种叫做ArrayList的泛型数组结构类型,提供相似的作用. 其 ...
- Source Xref 与 JavaDocs 学习理解
最近学习Mybatis的官方文档,看到了[项目文档]一节有很多内容没有见过,做个笔记,理解一下. 没找到java相关代码的解释,其实用下面这个php版本解释,也非常不错. What is SOURCE ...
- Java 最简单的计算器——使用Args参数
public class Test{ public static void main(String[] args){ if(args.length<3){ System.out.println( ...
- hdu 3666 Making the Grade
题目大意 给出了一列数,要求通过修改某些值,使得最终这列数变成有序的序列,非增或者非减的,求最小的修改量. 分析 首先我们会发现,最终修改后,或者和前一个数字一样,或者和后一个数字一样,这样才能修改量 ...
- 147. Insertion Sort List
Sort a linked list using insertion sort. 代码如下: /** * Definition for singly-linked list. * public cla ...
- 作业6 NABCD模型分析,产品Backlog
1.N(Need 需求): 随着生活水平的提高,每个家庭中都会有电脑和移动设备,可以更加快捷方便使用软件.以前孩子练习计算能力需要通做习题卷或老师出题目来进行,但现在只要通过这个四则运算的程序,可以自 ...
- sqlserver函数
SQLServer时间日期函数详解,SQLServer,时间日期, 1. 当前系统日期.时间 select getdate() 2. dateadd 在向指定日期加上一段时间的基础 ...