【转载】Lucene学习笔记(三)

三、用Lucene建立索引:
大纲:

1. Lucene索引的建立过程以及相关技术的简介
2. Lucene的文档格式
3. Lucene索引的优化
4. Lucene索引的同步机制
5. Lucene索引的格式

1. 索引建立的过程:大致分为以下四步"提取文本"、"构建Document"、"分析"和"建立索引"。

1.1 提取文本:为了使Lucene对文档数据建立索引,第一步就是要把这些要建立索引的文档数据转换成Lucene可以处理的类型!
在之前我们处理的要建立索引的文档都是纯文本类型的,这样做是为了省去一些比较赋值的麻烦。因为实际情况中会有各种格式的文档,
比如文档格式是PDF格式,如果想对它建立索引,那么首先必须从PDF文档中提取出文本内容,然后Lucene才可以对这些文本内容建立索引
因为Lucene只能对文本内容建立索引。

1.2 构建Document:第一步已经提取出文本内容,但是并没有建立索引,而是进入第二步构建Document对象(还需要Field对象),
构建完Document对象也是为建立索引做准备。<<至于构建Document对象的具体内容下边2标题中会明确讲解>>

1.3 分析与建立索引:完成前两步之后,就要调用IndexWriter对象的addDocument()方法使用Lucene建立索引。
其实再调用的addDocument()方法内部并不是立即就建立索引,而是先对要建立索引的数据进行分析(analysis),也即进行分词处理,
然后索引写入器会按照Lucene规定的索引格式将分词器处理完的数据写入索引文件,进而完成建立索引的过程!

2. Lucene的文档格式:就是说要建立索引的文档,加载到Lucene的程序中将会呈现的格式,
就是我们前面一直提到的Document(文档)和Field(字段)。
Document和Field在Lucene的索引过程中扮演着举足轻重的位置,同时在Lucene的搜索部分也会涉及到相应的概念,
因此可以说深入理解Document和Field是使用Lucene的基础。

2.1 Document(文档):可以说一个文档文本将会对应一个Document对象,因为,
从建立索引的角度说,将会把一个文件中的内容封装成为一个Document对象;
从搜索功能的角度说,返回的搜索结果是Document对象的集合,也就是说搜索到的每一个匹配者对应一个Document对象
所以要建立索引的文档与Document对象应该是一对一的关系!!

2.1.1 数据源的确立:对于一个文档来说,它提供的数据源可以是文件名、文件内容、文件最后一次修改的日期等。所以对于一个文档可以
提供多个数据源。因为一个文档对应一个Document对象,所以一个Document对象应该是多个数据源的集合,正因如此Lucene提供了Field对象,
用来分装每个数据源,也就是Document对象通过保存Field对象来实现对多个数据源的保存(文件名、文件内容等)。

2.1.2 换一个角度说:Lucene其实是对Document对象建立索引(因为是将要建立索引的文档的文本内容提取出来构建Document对象)

2.1.3 例子:将提取一个网页文档内容封装成一个Document对象
//创建一个空内容的Document对象
Document document = new Document();
//添加文档的路径信息到"path"字段(Field对象)中,然后加入到Document对象
Field pathField = Field.Keyword("path",fileObject.getPath());
document.add(pathField);
//添加文档最后一次修改时间信息到"lastModified"字段,这里用了DateField.timeToString方法对时间进行了转换,因为f.lastModified返回毫秒数
document.add(Field.Keyvalue("lastModified", DateField.timeToString(fileObject.lastModified()));
//添加文档内容到"content"字段
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileObject)));
document.add("content", reader);
//构建Document对象结束

**注意:代码中我们提到了path、lastModified和content字段,可以认为这是对应的Field对像的名字,
这些Field字段对象,在搜索的时候会有相应的作用。
想更一步了解Document对象的话可以查看Document类的源代码!
其实Document内部使用了一个List fields = new Vector();集合来保存多个Field对象。

2.1.4 使用Document的好处:使用Document来表示Lucene的一个抽象文档,好处就是它不和任何类型的文件相关联,
它是把对应文档中的数据进行了封装,无论要建立索引的文档类型原本是什么,Lucene都不用关心,
因为已经得到Document对象了就代表已经完成了从要建立索引的文档中提取数据信息的步骤,
到此步骤Lucene只管操作Document就可以建立索引了!

2.2 Field(字段):Field(字段)是与Document(文档)紧密相连的一个概念,在一个Document中,它代表了不同数据源的名称和对应的数据值,
即Field内封装的name属性表示名称,value属性代表的是数据源值;比如一个文件对应一个Document对象,文件中的数据源包括文件名、内容,
分别用Field对象来保存,Field.Textvalue("fileName",文件名);

2.2.1 然而实际中每个数据源提供的数据都不是一成不变的,就拿文件的内容这种数据源来说吧,一个文件中的内容可能会随时变化,
所以对于不同的数据源,即说成对于不同的字段,我们会需要Lucene提供不同的处理方式!
为此Lucene提供了三种处理方式:"是否切词"、"是否索引"、"是否存储"

2.2.1.1 是否切词:表示在该Field中的数据是否需要被切词(有关具体解释后面章节会讲解)

2.2.1.2 是否索引:表示在该Field中的数据是否在将来检索时需要被用户检索到。

**注意:如果不索引,只是用户通过该字段搜索的时候会搜不到对应该字段的数据,
即在建立索引的时候不对该字段对应的数据建立索引
(那有人会问不建索引还保存这个Field做什么?
可能是只想通过其他字段找到该字段对应的Document的时候可以
通过Document的get("Field的name")来得到这个字段的数据(当然如果也是"不存储"方法那么得到的将是空Null)
------个人理解)
正因为没有对该字段建立索引,所以搜索通过该字段建立的搜索条件的话就搜索不到内容了!
也就是说"是否索引"的意思就是是否对该Field字段中的内容建立索引

2.2.1.3 是否存储:表示在该Field中的数据是否要被原封不动的保存在索引当中!

**注意:从搜索的角度说,因为返回的搜索结果是Docuent对象,Document中的内容是索引中的信息,
所以Document中的Field就是刚开始建立索引时向Field中加入的数据,所以对于"不存储"的Field,
当从返回的Document中取出对应"不存储"字段的数据的时候将会是空的!
即不存储的,得到搜索结果后,通过Document.get("不存储处理方式的Field字段名");结果为Null

2.2.2 Lucene提供了4中静态方法可以构造Lucene定制好处理方式的Field,
也就是说用这些方法创建出来的Field就已经具有了上边规定的3种处理方式规则!

2.2.2.1 Text方法:通过该方法创建出来的Field字段对象中的内容将会被"切词"和
"索引(即可以通过对该Field字段建立搜索,搜索到对应的Document对象)",而是否"存储"要看调用的是那个Text方法。
Text(String,String)和Text(String,Reader)
调用的是前边的Text方法的话创建的Field对象中的内容将会被"存储"到索引,
调用的是后者的Text方法的话创建的Field对象中的内容将不会被"存储"到索引中,
因为这种传的是一个Reader流对象,对应的内容可能会很多,如果确实想存储内容,
那么要先从Reader中读取出内容,然后放入一个String类型的对象中,然后相当于调用的前一种Text方法来创建Field对象

2.2.2.2 Keyword方法:通过该方法创建的Field对象中的内容"不会被切词",但是会被"索引"和原封不动的"存储"到索引中,
通过该方法创建的Field适合存储URL地址、文件路径、时间日期、身份证号、姓名等数据量不大的信息。

2.2.2.3 UnIndexed方法:该方法创建的Field对象中的内容"不会被切词"和"不会被索引",只会被"存储"到索引中。
这种Field适合保存那些比较次要的和不经常作为关键字进行搜索,但是还需要和搜索到的结果一起显示在界面上的信息。

2.2.2.4 UnStored方法:和UnIndexed方法创建的Field正相反,创建的Field中的内容会被"切词"和"索引",但是不会被"存储"。
这类似与调用Text(String, Reader)方法的效果。适用场合也比较相似!

**注意: 以上这四种方法如果没有一个能够创建出满足我们需求的Field对象,那么我们可以自己通过new Field()的手段,
传递能够达到我们需求的那些参数来构建满足我们需求的Field对象。

**注意: 调用构造方法的时候一般我们会调用5个参数的构造方法,而不会调用6个参数包括storeTermVector参数的构造方法,
调用5个参数的方法的时候内部会调用6个参数的并为storeTermVector传递false值,这个值对于我们一般的查询不会有太大用处,
她是有关模糊查询的设置,所以可以不理会它的存在,所以建议直接调用5个参数的构造方法,即实现为storeTermVector参数传递默认的数值。

3. 索引的添加----IndexWriter类(索引写入器): 是Lucene中最重要的一个类,它的功能就是将文档写入到索引,同时控制写入索引过程中的各种参数。

3.1 初始化:IndexWriter的初始化并不复杂,它提供了很多重载的构造方法。所以构造一个IndexWriter对象比较容易。
通常我们使用3个参数的构造方法来构造IndexWriter对象:下面介绍3个具体的参数是什么

3.1.1 索引文件存储的目标路径:IndexWriter需要知道把索引创建到什么位置。
这个参数可以是一个String类型指定的路径字符串;
也可以是一个用File对象封装的路径地址;
也可以是Lucene提供的Directory类(后边将会详细讲解)

3.1.2 分析器:也可叫分词器,对要创建索引的文档内容进行分析切词,分析器类继承自org.apache.lucene.analysis.Analyzer类
就是在IndexWriter将文档内容写入索引之前,先通过分析器对文档内容进行分析切词,然后再写入索引

3.1.3 是否重新创建索引:IndexWriter创建索引之前还要检查一下是要重新创建索引还是进行增量添加索引。
就是当传入true的时候,IndexWriter向指定的保存索引的目录中保存索引之前,
不管目录中是否有已经有索引文件,一律全部清空,然后在保存索引文件;
当传入false的时候,那么IndexWriter保存索引的时候会在原来的基础上增量添加新的索引文件。
(注意:
这里说的增量不太明确,可能有两种意思,
一是在原文件的基础上在原文件内部添加新内容(经验证是这种方式);二是对新添加的索引文件建立一个新索引文件)

**注意:通常情况下,在一段代码中要把文档索引写入某个目录时,只能声明一个IndexWriter的对象来进行操作,
因为如果使用多个IndexWriter对象向同一个目录中写入索引时会引发很多的线程同步问题,后面将会具体讲解。

3.2 向目录中写入索引:就是将Document对象保存成索引文件。
方法就是调用IndexWriter对象的addDocument()方法,将Document保存到IndexWriter对象绑定好的存放索引文件的目录。

注意:调用完addDocument()方法之后一定要记住关闭IndexWriter索引写入器,即调用IndexWriter的close()方法,
如果不调用的话,虽然调用了addDocument()向目录写入索引文件,但是它并不会真正的立即将索引文件写入到目录磁盘中,
但是它确实是已经将Document对象生成了索引文件,只不过此时IndexWriter将生成好的索引文件保存在了内存中,
只有调用了IndexWriter的close方法之后,IndexWriter才会将内存中的索引文件真正的写入到目录磁盘中同时会关闭流对象。
如果不关闭IndexWriter对象的话,那么内存中已经生成的索引文件会因为没有调用哪个close方法而丢失。

3.3 使用IndexWriter对象调整控制性能参数:目的就是IndexWriter创建索引的同时还可以对一些创建索引过程中的参数进行设置与修改。
设置这些参数可以提升IndexWriter对象创建索引的性能。

3.3.1 mergeFactor参数:它用于控制Lucene在把索引从内存写入磁盘上的文件系统时内存中最大的Docunent数量,
同时还控制内存中最大的Segment数量。

例如:假设mergeFactor设置为10(默认值),当IndexWriter为第11个Document要建立索引到内存中时,它会建立一个Segment文件,
该文件包含了10个Document生成的索引文件,就这样一次一次运行,当Segment在磁盘中的数量达到10个的时候,
这10个Segment将会合并成一个Segment文件,此时按照开始10个Document生成的索引文件保存到一个Segment的规律,
那么此时的Segment文件中有100个Document生成的索引文件。就这样依次来推!

注意:我们可能对Segment不太了解,Segment是Lucene索引文件中最大的单位,
从上面的合并原则也可以看出,具体解析见后边。

3.3.2 maxMergeDocs参数:在mergeFactor中提到,当内存中的索引文件达到一定的数量,
就会合并成Segment文件并写到磁盘中,当Segment达到一定数量也会合并Segment文件。但是如果要建立索引的文档非常多的话,
到最后一个Segment文件的容量可能会非常的庞大,这样会很影响搜索的效率。、
因此Lucene提供了这个参数maxMergeDocs用来限制Segment文档中能容纳索引文件(Document生成的索引文件)的数量。

3.3.3 minMergeDocs参数:用于控制内存中持有的Document(或说成Document生成的索引文件)的数量的,
也就是说当达到这个数量的时候,内存中的文档都要刷新到(写入到)磁盘中保存。

3.4 限制Field的长度:在IndexWriter中提供了一个maxFieldLength属性参数来限定Field中保存的数据的长度,默认为10000.
就是说当我们采用默认值的时候,如果Field中保存的数据大小是10001个分词(这里的单位是分词,而不是字符长度或其他单位),
就是比如有一段话"我们是中国人",那么它保存到一个Field对象中的长度是2,因为分词后这段话就有两个分词"我们"和"中国人",
那么对该字段中数据创建索引的时候,就只会为前10000个分词创建索引,10000之后的就丢失了。

** Term的概念:个人理解应该是分词的封装类。就是在创建索引之前我先要对数据进行分析切词,
分成的每个词就是封装到Term中来进行保存。如“我们是中国人"分词后为"我们"和"中国人",
那么在Lucene中这两个分词的保存形式是以Term对象的形式存在的,就是把"我们"封装成一个Term对象,
把"中国人"也封装成一个Term对象------个人理解。

4.Lucene的索引文件格式简述:Lucene的索引文件有其固定的格式,这里我们列出一些重要的内容进行说明。

4.1 Segment(段):Segment是Lucene索引文件的一个最基本单位
一个Segment是一个独立的索引,可以通过IndexSearch进行单独查询搜索。
Lucene的工作其实就是不断的向磁盘中加入新的Segment段,然后再按照一定的算法合并Segment段以创建一个新的Segment段。

**注意:打开保存索引文件的目录我们会看到,目录中有一个且只能有一个segments文件,
该文件中保存了当前segments文件所在目录中相关的段的信息,比如有多少个Segment段、每个Segment段有多大等信息。

**注意:每个Segment的所有相关文件的前缀名都是一样的,如下表指定的目录中只有一个Segment段,
所以相关的文件名前都有相同的前缀"_1"。

例如:目录表:
_1,f1
_1,f2
_1,f3
_1,fdk
_1,fdx
_1,fnm
_1,frq
_1,prx
_1,tii
_1,tis
deletable
segments

4.2 索引文件:上边的目录中有很多不同后缀名的文件,我们一一认识一下:

.fnm :主要包括了各个域(字段)的名称信息(Field Name)
.fdx :主要包括了各个域(字段)的索引信息(Field Index)
.fdt :主要包括了各个域(字段)的内容信息(Field Data)
.tis :主要包括了词条(分词)的各种信息,有词条数量、间隔等(Term Info)
.tii :主要包括了词条的各个信息的索引(Term Info Index)
.frq :主要包括了词条的频率信息(Frequency)
.prx :主要包括了词条在文档中的位置信息(Position)
.f :主要包括了各个词条或文档的分值情况,用于对文档进行排序
deletable :主要包括了要删除的文档信息

4.3 复合索引格式:向上面那种索引文件的存储有一定的缺陷,
就是当创建的索引文件数量非常巨大的时候,系统要同时打开多个文件
(进行检索搜索或进行其他操作---个人理解,因为没有给出明确说明,所以自己理解),
这样对系统的资源来说可能是一种浪费,所以Lucene还提供了一种"复合索引"的格式。

例如:一个复合索引的目录
_1.cfs
deletable
segments

** 说明:在IndexWriter中有一个方法setUseCompoundFile(boolean),用来设置是否使用复合索引格式,默认是使用。

** 区别;与之前不同的地方就是在一个复合索引文件中每一个Segment段都有含有一个单独的.cfs文件

4.4 索引转换:如果开发者想要在"复合索引文件格式"和"多文件索引文件格式"之间相互转换需要一下步骤

4.4.1 生成一个IndexWriter对象打开索引

4.4.2 指定生成复合索引的类型

4.4.3 优化索引

4.4.4 关闭索引

**注意:这里要特别注意的是,一定要在构造IndexWriter对象时为IndexWriter类的构造方法传入"不重新创建索引文件",
即最后一个参数传入false值。

4.5 索引的存放位置------FSDirectory和RAMDirectory类:
Lucene提供了两种存放索引的位置,一种是磁盘(FSDirectory类)、一种是内存(RAMDirectory类);

问:我们会问之前我们也没有使用这两种方式怎么还能把索引文件保存到磁盘中呢?

答:那是因为之前我们使用IndexWriter(String, Analyzer, boolean)的时候虽然传入的是String类型的目录结构,
但是Lucene内部使用了FSDirectory类对象对String路径字符串进行了封装,所以才能将索引文件保存到磁盘当中。

4.5.1 在磁盘中存放索引文件------FSDirectory类:是File System Directory的缩写。
通常在使用FSDirectory的时候,Lucene会自动在内存中建立缓存,等到一定的程度就把索引写入磁盘
(就是我们前面提到的只有通过Document生成的索引信息达到一定量之后才会刷新到磁盘)。
当然这一步操作对用户来讲是透明的,因为用户看不见内存中的操作,只能看到写到磁盘中的索引文件的内容。

例子:
//创建第一个Document对象
Document doc1 = new Document();
doc1.add(Field.Text("name","word1 word2 word3"));
//创建第二个Document对象
Document doc2 = new Document();
doc2.add(Field.Text("name","word1 word2 word3"));
/*
创建索引写入器对象,此处传入的是FSDirectory对象
通过FSDirectory.getDirectory("", true)来创建
第一个参数是保存索引的路径
第二个参数是"是否要重新创建索引"和IndexWriter构造方法的最后一个参数一样!
*/
IndexWriter writer = new IndexWriter(FSDirectory.getDirectory("C://lucene_index", true), new StandardAnalyzer(), true);
//设置最大域长度为3个分词(Term)大小(注意这个域大小的设定主要是为下边要添加的这一个Field设定的----个人理解)
writer.maxFieldLength = 3;
writer.addDocument(doc1);
//设置最大域长度为3个分词(Term)大小
writer.maxFieldLength = 3;
writer.addDocument(doc2);
//关闭,将内存中缓存中没有写入到磁盘的索引内容通通写入磁盘
writer.close();

//开始进入搜索
IndexSearch search = new IndexSearch("C://lucene_index");
//接收结果集
Hits hits = null;
//封装条件对象,"word1"为要搜索的关键字,"name"是Field字段名称
Query query = QueryParser.parse("word1", "name", new StandardAnalyzer());
//获得结果
hits = search.search(query);
//打印结果
System.out.println("查询word1的结果条数为 " + hits.length());

//搜索word3关键字
Query query = QueryParser.parse("word3", "name", new StandardAnalyzer());
hits = search.search(query);
System.out.println("查询word3的结果条数为 " + hits.length());

4.5.2 在内存中存放索引文件------RAMDirectory类:从功能上讲,FSDirectory类的对象在磁盘上能操作的事,
RAMDirectory类的对象在内存中都能完成,并且会有更快的速度。

缺点:就是在JVM退出后,内存中保存的索引信息数据将不复存在!

例子:
//创建两个Document对象,同上边的代码
//使用RAMDirectory创建IndexWriter对象,直接通过调用缺省的构造方法创建就行了
IndexWriter writer = new IndexWriter(new RAMDirectory(), new StandardAnalyzer(), true);
//设置Field字段的大小,为一个分词大小,说明doc1中的Field的大小是一个分词大小
writer.maxFieldLength = 1;
writer.addDocument(doc1);
//设置大小为3个分词大小,说明doc2中的Field的大小长度为三个分词
writer.maxFieldLength = 3;
writer.addDocument(doc2);
//关闭
writer.close();

//开始搜索,同上;但是搜索结果将不会一样,因为这里的doc1中的Field长度只为一个分词,所以只能将word1作为索引保存,
所以当通过搜索关键子word3搜索是只能搜索到一个结果,因为doc1中的Field中没有word3的信息。

缺点:唯一的缺点就是内存中的索引没有得到保存,而是随着JVM的关闭索引信息也跟着消失了,
下一小节中我们会加入一个手段,在JVM关闭之前把内存中的索引写入到磁盘中!

4.5.3 索引的合并: 就是创建一个FSDirectory类型的IndexWriter对象,创建一个RAMDirectory类型的IndexWriter对象,
然后向每个IndexWriter中都保存一个索引文件,即向磁盘中保存一个索引文件,向内存中也保存一个索引文件,
因为内存中的JVM关闭后会消失,所以这里我让内存中的和磁盘中的索引文件合并,都合并到磁盘中,
这样就解决了内存中索引文件丢失的问题。但是合并过程有一些细节需要我们特别注意一下!!

例子:
//创建一个磁盘Directory和一个内存Directory对象
FSDirectory fsDir = FSDirectory.getDirectory("C://lucene_index", true);
RAMDirectory ramDir = new RAMDirectory();
//分别创建对应的IndexWriter对象
IndexWriter fsWriter = new IndexWriter(fsDir, new StandardAnalyzer(), true);
IndexWriter ramWriter = new IndexWriter(ramDir, new StandardAnalyzer(), true);
//创建两个Document文档对象,同上

//向内存中保存索引信息
ramWriter.addDocument(doc1);
//向磁盘中保存索引信息
fsWriter.addDocument(doc2);

//索引创建完之后,为了能够合并不出问题要先将内存的索引写入器关闭,确保所有的索引都写入到了内存中
ramWriter.close();

/*
合并索引文件,传递参数是为了合并,首先要读出合并目标目录中的所有索引,加载到内存中,
然后和现在内存中拥有的所有的索引组合起来,然后重新写入到目标目录,以达到合并的目的
*/
fsWriter.addIndexs(new Directory[]{fsDir);

//合并完成后就可以反比磁盘索引写入器了
fsWriter.close();

//下边开始搜索,同上

** 注意:这里要特别小心的地方就是合并索引之前,一定要先关闭内存写入器,然后在合并,
先关闭内存写入器的目的是让内存写入器确保所有要保存到内存中的索引全部写入到内存中,
这样才能保证合并的成功,如果不这样先关闭内存索引写入器的话,合并的时候会报错误信息。

** 注意:如果是要合并两个FSDirectory的话,方法和上边一样。

4.6 从索引中删除Document(文档):索引的维护中很重要的一部分就是删除没有用处的Document文档。

**声明:下面为什么不说成删除索引中的Document文档呢?
因为通过Lucene写入索引或者搜索索引的时候,通过Lucene提供的API先要将数据加载到内存才能保存索引或者搜索索引,
而恰恰在内存中索引存在的形式是以一个个Document对象存在的,所以下面我们就直接说成了删除Document文档,
而没有说成删除所以中的Document文档。这些自己理解一下就行了!

4.6.1 删除Document文档的工具类----IndexReader类:
创建方法可以通过IndexReader.open()方法,传递索引保存的地址就可以了。
这样IndexReader对象就可以对指定目录中保存的索引进行操作了。
《具体方法声明可以查看Javadoc API文档》

4.6.2 删除与反删除某个特定的Document文档:就是通过IndexReader对象的delete(int)方法,
就可以删除指定索引值(可以叫做位置值,和for循环中遍历的索引值类似,只不过这个值是Document保存的位置值),
这样就可以删除指定位置的Document文档。

例如:
//创建两个Document文档,同上
......
//创建索引写入器
IndexWriter writer = new IndexWriter("C://lucene_index", new StandardAnalyzer(), true);
/*
添加Document对象进行保存索引
** 注意:这里存在了位置值,第一个保存添加的Document的位置值是0,之后的一次累加
*/
writer.addDocument(doc1);
writer.addDocument(doc2);
writer.close();

//创建IndexReader对象以便对索引进行操控:传入索引保存目录地址
IndexReader reader = IndexReader.open("C://lucene_index");
//删除第一个保存添加的Document文档
reader.delete(0);
//关闭流
reader.close();

//搜索:以便通过搜索结果验证是否成功的删除,代码同上
......

** 缺点:通过这种delete方法删除,只适合与知道Document文档位置的情况,如果保存的Document数量比较大,
我想要删除里面的保存Field字段名为"name",并且分词中有"word1"的那个Document,很明显这种方法是做不到的。

** 注意:其实delete方法并不是真正的将Document文档信息删除了,只是对对应的Document文档对象的一个"是否删除"的属性
标记为真,这样就代表了该Document刚刚做了一个删除操作,这样在搜索的时候就找不到了,如果我们想恢复删除,
只须调用IndexReader类的undeleteAll()方法,就可以恢复索引删除状态的Document文档,
这样这些恢复了的Document文档在搜索的时候就可以找到了。

4.6.3 按Field字段来删除对应的Document文档:就是按照指定的Field字段的名称,
和字段中保存的数据被分词后的某个分词进行查找,找到后删除对应的Document文档,
这样对于满足上面的需求就比较容易了。

例子:
//创建两个Document对象,同上
......
//创建索引写入器,并且保存两个Document文档到索引并且关闭,同上
......
//创建IndexReader对象
IndexReader reader = IndexReader.open("C://lucene_index");
/*
删除包含指定分词的Document文档
** 注意:Term代表一个分词,new Term()的第一个参数是字段名称,第二个参数是哪个分词,
最后将会找到包含有指定字段名的Field中同时也包含指定的分词的Document文档,此时将会把它删除
*/
reader.delete(new Term("name", "word1"));
//关闭流
reader.close();

//搜索代码,同上
......

** 说明:这种方式比较常用,比如删除某一网站下的所有Web文档,并且知道该网站的域名是www.abc.com
则可以这样删除,reader.delete(new Term("domain", "www.abc.com"));

4.7 Lucene的索引优化:建立完索引之后,并不是原封不动的放置在文件系统中,而是对其进行适当的优化。

4.7.1 为什么要优化索引:优化索引就是就是通过合并磁盘上的索引文件,以便减少文件的数量,从而也减少搜索索引的时间。
虽然可以通过控制性能参数来改变磁盘上的Segment索引文件数量。但是建立完索引文件后,
任然可能存在大量未进行合并的Segment索引文件。

又因为在搜索的时候,主要就是和磁盘上的所有索引文件打交道,如果搜索的Segment索引文件数量过大,
那么搜索器进行搜索的时候必然会增多同时打开的Segment索引文件,这样如果在一个多线程的环境下,
多个用户同时进行搜索就是同时打开大量的索引文件,这样会大大降低检索的效率,也会增大服务器的负载。

所以还需要额外的优化手段!!

4.7.2 优化索引的方法:使用IndexWriter类的optimize()方法,它会把磁盘上的多个Segment索引文件进行合并,
组成一个全新的Segment索引文件。

** 注意:optimize()方法并不会提升建立索引的速度,相反,它会降低建立索引的速度。
而且由于在合并索引的时候需要额外的磁盘空间来创建新的Segment索引文件,因此它对磁盘空间的要求也会增加。

** 使用原则:通常情况下,optimize()方法不应当在索引建立的过程中调用,而应当是完成建立大批量索引之后再进行调用.

** 原因:因为在对索引优化的时候,磁盘的I/O操作频繁,如果太过频繁的进行索引优化,会导致系统吞吐量大幅度降低。
因此建议当你一次性要为大量文档建立索引时,最后再建完后再进行索引的优化。
如果是频繁的索引增加,但是每次增加的周期比较长,则可以制定一个适合的周期进行索引的优化。

4.8 Lucene索引的同步机制(内部实现,仅供了解):这是一个非常重要的话题,很好的理解它们是正确使用Lucene的一个关键。

4.8.1 同步法则:在Lucene的操作过程中,有很多操作对都能对索引进行修改。如果在一个多线程的环境下,
那么很容易出现同步问题,所以Lucene中有以下同步法则:这些法则是用来提醒我们开发的时候不要违背,如果违背程序会报错。

4.8.1.1 任何数量的只读操作可以被同时执行,比如使用IndexSearch对索引进行检索。

4.8.1.2 任何数量的只读操作都可以在索引正在被修改时执行,
这里的"修改"指的是有进程正在向索引中添加新的Document文档、删除旧文档或者是索引正在进行优化的过程。

4.8.1.3 在同一时间内,只可以有一个修改索引的进程对索引进行操作。
也就是说在同一时间内,只可以有一个IndexWriter或是IndexReader获取对索引的使用权。

4.8.2 Lucene的索引"锁":为了保证Lucene的索引不被我操作所破坏,Lucene设置了文件锁。
这些锁默认情况下均放置在系统的临时文件夹下,这个文件夹的路径是由java中的java.io.tempdir系统变量所指定的。
Lucene中提供了两种锁:writer.lock和commit.lock锁

4.8.2.1 writer.lock锁:该锁的主要目的是为了防止多个线程同时修改一个索引文件而设置的。通常情况下,
它由IndexWriter在初始化时获得,而在关闭时释放;另外当IndexReader从索引中删除文档或反删除文档时也会获得该锁。

4.8.2.2 commit.lock锁:主要是在索引的Segment被建立、合并或读取时生成的。
它会在IndexReader读取各个Segment信息之时被获取,在完成读取之时释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫欺少年穷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值